diff --git a/tailbone/static/js/tailbone.buefy.grid.js b/tailbone/static/js/tailbone.buefy.grid.js
deleted file mode 100644
index 6be28f41..00000000
--- a/tailbone/static/js/tailbone.buefy.grid.js
+++ /dev/null
@@ -1,167 +0,0 @@
-
-const GridFilterNumericValue = {
-    template: '#grid-filter-numeric-value-template',
-    props: {
-        value: String,
-        wantsRange: Boolean,
-    },
-    data() {
-        return {
-            startValue: null,
-            endValue: null,
-        }
-    },
-    mounted() {
-        if (this.wantsRange) {
-            if (this.value.includes('|')) {
-                let values = this.value.split('|')
-                if (values.length == 2) {
-                    this.startValue = values[0]
-                    this.endValue = values[1]
-                } else {
-                    this.startValue = this.value
-                }
-            } else {
-                this.startValue = this.value
-            }
-        } else {
-            this.startValue = this.value
-        }
-    },
-    watch: {
-        // when changing from e.g. 'equal' to 'between' filter verbs,
-        // must proclaim new filter value, to reflect (lack of) range
-        wantsRange(val) {
-            if (val) {
-                this.$emit('input', this.startValue + '|' + this.endValue)
-            } else {
-                this.$emit('input', this.startValue)
-            }
-        },
-    },
-    methods: {
-        focus() {
-            this.$refs.startValue.focus()
-        },
-        startValueChanged(value) {
-            if (this.wantsRange) {
-                value += '|' + this.endValue
-            }
-            this.$emit('input', value)
-        },
-        endValueChanged(value) {
-            value = this.startValue + '|' + value
-            this.$emit('input', value)
-        },
-    },
-}
-
-Vue.component('grid-filter-numeric-value', GridFilterNumericValue)
-
-
-const GridFilterDateValue = {
-    template: '#grid-filter-date-value-template',
-    props: {
-        value: String,
-        dateRange: Boolean,
-    },
-    data() {
-        return {
-            startDate: null,
-            endDate: null,
-        }
-    },
-    mounted() {
-        if (this.dateRange) {
-            if (this.value.includes('|')) {
-                let values = this.value.split('|')
-                if (values.length == 2) {
-                    this.startDate = values[0]
-                    this.endDate = values[1]
-                } else {
-                    this.startDate = this.value
-                }
-            } else {
-                this.startDate = this.value
-            }
-        } else {
-            this.startDate = this.value
-        }
-    },
-    methods: {
-        focus() {
-            this.$refs.startDate.focus()
-        },
-        startDateChanged(value) {
-            if (this.dateRange) {
-                value += '|' + this.endDate
-            }
-            this.$emit('input', value)
-        },
-        endDateChanged(value) {
-            value = this.startDate + '|' + value
-            this.$emit('input', value)
-        },
-    },
-}
-
-Vue.component('grid-filter-date-value', GridFilterDateValue)
-
-
-const GridFilter = {
-    template: '#grid-filter-template',
-    props: {
-        filter: Object
-    },
-
-    methods: {
-
-        changeVerb() {
-            // set focus to value input, "as quickly as we can"
-            this.$nextTick(function() {
-                this.focusValue()
-            })
-        },
-
-        valuedVerb() {
-            /* this returns true if the filter's current verb should expose value input(s) */
-
-            // if filter has no "valueless" verbs, then all verbs should expose value inputs
-            if (!this.filter.valueless_verbs) {
-                return true
-            }
-
-            // if filter *does* have valueless verbs, check if "current" verb is valueless
-            if (this.filter.valueless_verbs.includes(this.filter.verb)) {
-                return false
-            }
-
-            // current verb is *not* valueless
-            return true
-        },
-
-        multiValuedVerb() {
-            /* this returns true if the filter's current verb should expose a multi-value input */
-
-            // if filter has no "multi-value" verbs then we safely assume false
-            if (!this.filter.multiple_value_verbs) {
-                return false
-            }
-
-            // if filter *does* have multi-value verbs, see if "current" is one
-            if (this.filter.multiple_value_verbs.includes(this.filter.verb)) {
-                return true
-            }
-
-            // current verb is not multi-value
-            return false
-        },
-
-        focusValue: function() {
-            this.$refs.valueInput.focus()
-            // this.$refs.valueInput.select()
-        }
-    }
-}
-
-Vue.component('grid-filter', GridFilter)
diff --git a/tailbone/templates/base.mako b/tailbone/templates/base.mako
index e1020b28..d8e86547 100644
--- a/tailbone/templates/base.mako
+++ b/tailbone/templates/base.mako
@@ -3,6 +3,7 @@
 <%namespace file="/autocomplete.mako" import="tailbone_autocomplete_template" />
 <%namespace name="base_meta" file="/base_meta.mako" />
 <%namespace file="/formposter.mako" import="declare_formposter_mixin" />
+<%namespace file="/grids/filter-components.mako" import="make_grid_filter_components" />
 <%namespace name="page_help" file="/page_help.mako" />
 <%namespace name="multi_file_upload" file="/multi_file_upload.mako" />
 
@@ -90,7 +91,6 @@
   ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.numericinput.js') + '?ver={}'.format(tailbone.__version__))}
   ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.oncebutton.js') + '?ver={}'.format(tailbone.__version__))}
   ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.timepicker.js') + '?ver={}'.format(tailbone.__version__))}
-  ${h.javascript_link(request.static_url('tailbone:static/js/tailbone.buefy.grid.js') + '?ver={}'.format(tailbone.__version__))}
 
   
-
-
-
-
-
 
+  
+%def>
+
+<%def name="make_grid_filter_date_value_component()">
+  
+  
+%def>
+
+<%def name="make_grid_filter_component()">
+  
+  
+%def>
diff --git a/tailbone/templates/master/index.mako b/tailbone/templates/master/index.mako
index 051a9ab6..d9dabc7b 100644
--- a/tailbone/templates/master/index.mako
+++ b/tailbone/templates/master/index.mako
@@ -299,6 +299,11 @@
   % endif
 %def>
 
+<%def name="make_grid_component()">
+  ## TODO: stop using |n filter?
+  ${grid.render_complete(tools=capture(self.grid_tools).strip(), context_menu=capture(self.context_menu_items).strip())|n}
+%def>
+
 <%def name="render_grid_component()">
   <${grid.component} ref="grid" :csrftoken="csrftoken"
      % if master.deletable and request.has_perm('{}.delete'.format(permission_prefix)) and master.delete_confirm == 'simple':
@@ -309,11 +314,16 @@
 %def>
 
 <%def name="make_this_page_component()">
+
+  ## define grid
+  ${self.make_grid_component()}
+
   ${parent.make_this_page_component()}
-  
@@ -323,13 +333,6 @@
   ${self.page_content()}
 %def>
 
-<%def name="render_this_page_template()">
-  ${parent.render_this_page_template()}
-
-  ## TODO: stop using |n filter
-  ${grid.render_complete(tools=capture(self.grid_tools).strip(), context_menu=capture(self.context_menu_items).strip())|n}
-%def>
-
 <%def name="modify_this_page_vars()">
   ${parent.modify_this_page_vars()}