diff --git a/tailbone/static/js/tailbone.buefy.message_recipients.js b/tailbone/static/js/tailbone.buefy.message_recipients.js
new file mode 100644
index 00000000..161ad404
--- /dev/null
+++ b/tailbone/static/js/tailbone.buefy.message_recipients.js
@@ -0,0 +1,108 @@
+
+const MessageRecipients = {
+    template: '#message-recipients-template',
+
+    props: {
+        name: String,
+        value: Array,
+        possibleRecipients: Array,
+        recipientDisplayMap: Object,
+    },
+
+    data() {
+        return {
+            autocompleteValue: null,
+            actualValue: this.value,
+        }
+    },
+
+    computed: {
+
+        filteredData() {
+            // this is the logic responsible for "matching" user's autocomplete
+            // input, with possible recipients.  we return all matches as list.
+            let filtered = []
+            if (this.autocompleteValue) {
+                let term = this.autocompleteValue.toLowerCase()
+                this.possibleRecipients.forEach(function(value, key, map) {
+
+                    // first check to see if value is simple string, if so then
+                    // will attempt to match it directly
+                    if (value.toLowerCase !== undefined) {
+                        if (value.toLowerCase().indexOf(term) >= 0) {
+                            filtered.push({value: key, label: value})
+                        }
+
+                    } else {
+                        // value is not a string, which means it must be a
+                        // grouping object, which must have a name property
+                        if (value.name.toLowerCase().indexOf(term) >= 0) {
+                            filtered.push({
+                                value: key,
+                                label: value.name,
+                                moreValues: value.uuids,
+                            })
+                        }
+                    }
+                })
+            }
+            return filtered
+        },
+    },
+
+    methods: {
+
+        addRecipient(uuid) {
+
+            // add selected user to "actual" value
+            if (!this.actualValue.includes(uuid)) {
+                this.actualValue.push(uuid)
+            }
+        },
+
+        removeRecipient(uuid) {
+
+            // locate and remove user uuid from "actual" value
+            for (let i = 0; i < this.actualValue.length; i++) {
+                if (this.actualValue[i] == uuid) {
+                    this.actualValue.splice(i, 1)
+                    break
+                }
+            }
+        },
+
+        selectionMade(option) {
+
+            // apparently option can be null sometimes..?
+            if (option) {
+
+                // add all newly-selected users to "actual" value
+                if (option.moreValues) {
+                    // grouping object; add all its "contained" values
+                    option.moreValues.forEach(function(uuid) {
+                        this.addRecipient(uuid)
+                    }, this)
+                } else {
+                    // normal object, just add its value
+                    this.addRecipient(option.value)
+                }
+
+                // let parent know we changed value
+                this.$emit('input', this.actualValue)
+            }
+
+            // clear out the *visible* autocomplete value
+            this.$nextTick(function() {
+                this.autocompleteValue = null
+
+                // TODO: wtf, sometimes we have to clear this out twice?!
+                this.$nextTick(function() {
+                    this.autocompleteValue = null
+                })
+            })
+        },
+    },
+}
+
+
+Vue.component('message-recipients', MessageRecipients)
diff --git a/tailbone/templates/deform/message_recipients_buefy.pt b/tailbone/templates/deform/message_recipients_buefy.pt
new file mode 100644
index 00000000..1ea5e153
--- /dev/null
+++ b/tailbone/templates/deform/message_recipients_buefy.pt
@@ -0,0 +1,13 @@
+
diff --git a/tailbone/templates/deform/textinput.pt b/tailbone/templates/deform/textinput.pt
index 48d4c360..2e1c32ef 100644
--- a/tailbone/templates/deform/textinput.pt
+++ b/tailbone/templates/deform/textinput.pt
@@ -4,13 +4,16 @@
                   mask mask|field.widget.mask;
                   mask_placeholder mask_placeholder|field.widget.mask_placeholder;
                   style style|field.widget.style;
-                  use_buefy use_buefy|0;"
+                  use_buefy use_buefy|0;
+                  placeholder placeholder|getattr(field.widget, 'placeholder', '');
+                  autocomplete autocomplete|getattr(field.widget, 'autocomplete', 'on');"
       tal:omit-tag="">
   
     
     
   ${self.validate_message_js()}
+  % endif
 %def>
 
 <%def name="validate_message_js()">
@@ -95,6 +102,19 @@
 
 <%def name="extra_styles()">
   ${parent.extra_styles()}
+  % if use_buefy:
+      
+  % else:
   ${h.stylesheet_link(request.static_url('tailbone:static/css/jquery.tagit.css'))}
   
+  % endif
 %def>
 
 <%def name="before_tag_added()">
@@ -133,4 +154,20 @@
   % endif
 %def>
 
+<%def name="render_this_page_template()">
+  ${parent.render_this_page_template()}
+  ${message_recipients_template()}
+%def>
+
+<%def name="modify_this_page_vars()">
+  ${parent.modify_this_page_vars()}
+  
+%def>
+
+
 ${parent.body()}
diff --git a/tailbone/templates/messages/recipients.mako b/tailbone/templates/messages/recipients.mako
new file mode 100644
index 00000000..c16ddaf2
--- /dev/null
+++ b/tailbone/templates/messages/recipients.mako
@@ -0,0 +1,36 @@
+## -*- coding: utf-8; -*-
+
+<%def name="message_recipients_template()">
+  
+%def>
diff --git a/tailbone/templates/messages/view.mako b/tailbone/templates/messages/view.mako
index a7bf449c..78caab93 100644
--- a/tailbone/templates/messages/view.mako
+++ b/tailbone/templates/messages/view.mako
@@ -34,6 +34,7 @@
         }
         .tailbone-message-body {
             margin: 1rem auto;
+            min-height: 10rem;
         }
         .tailbone-message-body p {
             margin-bottom: 1rem;
diff --git a/tailbone/templates/themes/falafel/base.mako b/tailbone/templates/themes/falafel/base.mako
index 01395ea7..b3ca7d54 100644
--- a/tailbone/templates/themes/falafel/base.mako
+++ b/tailbone/templates/themes/falafel/base.mako
@@ -246,7 +246,7 @@
               % if master:
                   % if master.listing:
                       ${index_title}
-                  % else:
+                  % elif index_url:
                       ${h.link_to(index_title, index_url)}
                       % if parent_url is not Undefined:
                           »
@@ -258,6 +258,8 @@
                       % if master.viewing and grid_index:
                           ${grid_index_nav()}
                       % endif
+                  % else:
+                      ${index_title}
                   % endif
               % elif index_title:
                   ${index_title}
diff --git a/tailbone/views/messages.py b/tailbone/views/messages.py
index 0d356d70..5f814569 100644
--- a/tailbone/views/messages.py
+++ b/tailbone/views/messages.py
@@ -2,7 +2,7 @@
 ################################################################################
 #
 #  Rattail -- Retail Software Framework
-#  Copyright © 2010-2018 Lance Edgar
+#  Copyright © 2010-2019 Lance Edgar
 #
 #  This file is part of Rattail.
 #
@@ -36,6 +36,7 @@ from deform import widget as dfwidget
 from pyramid import httpexceptions
 from webhelpers2.html import tags, HTML
 
+# from tailbone import forms
 from tailbone.db import Session
 from tailbone.views import MasterView
 from tailbone.util import raw_datetime
@@ -140,6 +141,8 @@ class MessagesView(MasterView):
         return six.text_type(sender)
 
     def render_subject_bold(self, message, field):
+        if not message.subject:
+            return ""
         return HTML.tag('span', c=message.subject, style='font-weight: bold;')
 
     def render_recipients(self, message, column_name):
@@ -212,9 +215,12 @@ class MessagesView(MasterView):
         super(MessagesView, self).configure_form(f)
         use_buefy = self.get_use_buefy()
 
-        # we have custom logic to disable submit button
-        f.auto_disable = False
-        f.auto_disable_save = False
+        f.submit_label = "Send Message"
+
+        if not use_buefy:
+            # we have custom logic to disable submit button
+            f.auto_disable = False
+            f.auto_disable_save = False
 
         # TODO: A fair amount of this still seems hacky...
 
@@ -223,22 +229,34 @@ class MessagesView(MasterView):
 
         f.set_type('sent', 'datetime')
 
+        # recipients
         f.set_renderer('recipients', self.render_recipients_full)
         f.set_label('recipients', "To")
 
+        # subject
         if use_buefy:
             f.set_renderer('subject', self.render_subject_bold)
+            if self.creating:
+                f.set_widget('subject', dfwidget.TextInputWidget(
+                    placeholder="please enter a subject",
+                    autocomplete='off'))
+                f.set_required('subject')
 
+        # body
         f.set_widget('body', dfwidget.TextAreaWidget(cols=50, rows=15))
 
         if self.creating:
             f.remove('sender', 'sent')
 
-            f.insert_after('recipients', 'recipients_')
+            # recipients
+            f.insert_after('recipients', 'set_recipients')
             f.remove('recipients')
-            f.set_node('recipients_', colander.SchemaNode(colander.Set()))
-            f.set_widget('recipients_', RecipientsWidget())
-            f.set_label('recipients_', "To")
+            f.set_node('set_recipients', colander.SchemaNode(colander.Set()))
+            if use_buefy:
+                f.set_widget('set_recipients', RecipientsWidgetBuefy())
+            else:
+                f.set_widget('set_recipients', RecipientsWidget())
+            f.set_label('set_recipients', "To")
 
             if self.replying:
                 old_message = self.get_instance()
@@ -259,11 +277,11 @@ class MessagesView(MasterView):
                     value = [r[0] for r in value]
                     if old_message.sender is not self.request.user and old_message.sender.active:
                         value.insert(0, old_message.sender_uuid)
-                    f.set_default('recipients_', ','.join(value))
+                    f.set_default('set_recipients', ','.join(value))
 
                 # Just a normal reply, to sender only.
                 elif self.filter_reply_recipient(old_message.sender):
-                    f.set_default('recipients_', old_message.sender.uuid)
+                    f.set_default('set_recipients', old_message.sender.uuid)
 
                 # TODO?
                 # # Set focus to message body instead of recipients, when replying.
@@ -281,7 +299,7 @@ class MessagesView(MasterView):
             if self.request.user:
                 message.sender = self.request.user
 
-            for uuid in data['recipients_']:
+            for uuid in data['set_recipients']:
                 user = self.Session.query(model.User).get(uuid)
                 if user:
                     message.add_recipient(user, status=self.enum.MESSAGE_STATUS_INBOX)
@@ -322,11 +340,21 @@ class MessagesView(MasterView):
                 return recipient
 
     def template_kwargs_create(self, **kwargs):
-        recips = list(self.get_available_recipients().items())
+        use_buefy = self.get_use_buefy()
+
+        recips = self.get_available_recipients()
+        if use_buefy:
+            kwargs['recipient_display_map'] = recips
+        recips = list(recips.items())
         recips.sort(key=self.recipient_sortkey)
         kwargs['available_recipients'] = recips
+
         if self.replying:
             kwargs['original_message'] = self.get_instance()
+
+        if use_buefy:
+            kwargs['index_url'] = None
+            kwargs['index_title'] = "New Message"
         return kwargs
 
     def recipient_sortkey(self, recip):
@@ -355,7 +383,7 @@ class MessagesView(MasterView):
         kwargs['message'] = message
         kwargs['recipient'] = recipient
 
-        if recipient.status == self.enum.MESSAGE_STATUS_ARCHIVE:
+        if recipient and recipient.status == self.enum.MESSAGE_STATUS_ARCHIVE:
             kwargs['index_url'] = self.request.route_url('messages.archive')
 
         return kwargs
@@ -514,6 +542,30 @@ class RecipientsWidget(dfwidget.TextInputWidget):
         return pstruct.split(',')
 
 
+class RecipientsWidgetBuefy(dfwidget.Widget):
+    """
+    Custom "message recipients" widget, for use with Buefy / Vue.js themes.
+    """
+    template = 'message_recipients_buefy'
+
+    def deserialize(self, field, pstruct):
+        if pstruct is colander.null:
+            return colander.null
+        if not isinstance(pstruct, six.string_types):
+            raise colander.Invalid(field.schema, "Pstruct is not a string")
+        if not pstruct:
+            return colander.null
+        pstruct = pstruct.split(',')
+        return pstruct
+
+    def serialize(self, field, cstruct, **kw):
+        if cstruct in (colander.null, None):
+            cstruct = ""
+        template = self.template
+        values = self.get_template_values(field, cstruct, kw)
+        return field.renderer(template, **values)
+
+
 def includeme(config):
 
     config.add_tailbone_permission('messages', 'messages.list', "List/Search Messages")