demo D54
The filter menu in htmx
Per-column filtering with the operators each type supports, plus an inline filter row that writes the same condition
filterRow · filters.set(tree)
This grid filters each column from a menu that offers only the operators that make sense for that column's type. It is the default way a reader narrows a table without leaving the grid, with the right controls for text, numbers and dates each. Switch on the filter row above the grid and the same condition can be typed straight under the heading instead.
This is the htmx version. A server-rendered table is upgraded in place, so the page is readable before any script runs and gains the full grid when the module loads, which suits a progressively enhanced app.
The configuration
<link rel="stylesheet" href="https://cdn.latticegrid.dev/lattice-grid@1.107.0/lattice-grid.min.css">
<!-- A real, server-rendered table: readable with JavaScript off. data-field on
each header names the column that cell becomes, and data-id on each row is
the key the grid carries it under. -->
<table id="filter-menu-table" data-lattice-grid>
<thead>
<tr>
<th data-field="service">Service</th>
<th data-field="team">Team</th>
<th data-field="owner">Owner</th>
<th data-field="region">Region</th>
<th data-field="active">Active</th>
<th data-field="deprecated">Deprecated</th>
<th data-field="version">Version</th>
<th data-field="instances">Instances</th>
<th data-field="cpuCores">vCPU</th>
<th data-field="memoryGb">Memory GB</th>
</tr>
</thead>
<tbody>
<tr data-id="1"><td>svc-00001</td><td>Search</td><td>k.yilmaz</td><td>Malmö</td><td>false</td><td>false</td><td>1.2.10</td><td>49</td><td>32</td><td>256</td></tr>
<tr data-id="2"><td>svc-00002</td><td>Mobile</td><td>m.svensson</td><td>Leeds</td><td>true</td><td>false</td><td>2.3.6</td><td>162</td><td>20</td><td>104</td></tr>
<tr data-id="3"><td>svc-00003</td><td>Identity</td><td>j.okafor</td><td>Zürich</td><td>true</td><td>false</td><td>1.11.1</td><td>20</td><td>17</td><td>212</td></tr>
<!-- the rest of the rows, rendered by your server -->
</tbody>
</table>
<script type="module">
import { hydrateTable } from 'https://cdn.latticegrid.dev/lattice-grid@1.107.0/modules/htmx.esm.min.js';
// Every column carries a filter, so the header menu offers the kind that
// fits its type: text, set, number or date.
// The markup carries the rows; a server-rendered table's column types are
// declared in the config hydrateTable takes. The grid replaces the table
// where it stood.
const grid = hydrateTable(document.getElementById('filter-menu-table'), {
toolPanel: { side: 'left', panels: ['filters', 'columns'], actions: ['restore'], exportName: 'lattice-demo' },
columns: [
{ field: 'service', title: 'Service', layout: { pin: 'start', width: 140 }, filter: true },
{ field: 'team', title: 'Team', filter: true },
{ field: 'owner', title: 'Owner', filter: true },
{ field: 'tier', title: 'Tier', cell: { decoration: 'pill', variant: { map: { gold: 'warning', silver: 'neutral', bronze: 'info' } } }, filter: true },
{ field: 'state', title: 'State', cell: { decoration: 'dot', variant: { map: { healthy: 'success', degraded: 'warning', failing: 'danger', unknown: 'neutral' } } }, filter: true },
{ field: 'region', title: 'Region', filter: true },
{ field: 'active', title: 'Active', type: 'boolean', filter: true },
{ field: 'deprecated', title: 'Deprecated', type: 'boolean', filter: true },
{ field: 'version', title: 'Version', layout: { width: 100 }, filter: true },
{ field: 'instances', title: 'Instances', type: 'number', filter: true },
{ field: 'cpuCores', title: 'vCPU', type: 'number', filter: true },
{ field: 'memoryGb', title: 'Memory GB', type: 'number', filter: true },
],
});
</script>
Per-column filtering with type-aware operators
A filter menu is the default way a user narrows a table without leaving the grid: click a header’s filter icon, and a panel opens with the operators that make sense for that column’s data. A JavaScript data grid handling mixed content needs this to be type-aware rather than one generic text box, since “contains” is the right default for a name column, “between” for a price column, and “is after” for a date column. Lattice Grid infers the operator set from the column’s declared type, then falls back to a text-contains filter for anything undeclared, so the menu never offers a numeric range on a string field.
The filters.set(tree) call applies a filter programmatically, taking the same tree structure the menu builds when a user picks conditions and combines them with AND or NOR. Passing a tree through this call and opening the menu afterwards shows the equivalent conditions already selected, so the UI and the API stay in sync. Filtering runs against the underlying row model rather than the rendered DOM, so narrowing a column on a dataset with hundreds of thousands of rows does not require re-measuring rows that stay hidden. Each filter menu is keyboard-reachable: Enter on a header’s filter icon opens the panel, and focus moves straight to the first operator control.
How do you filter a column programmatically instead of through the menu UI?
Call filters.set(tree) with a condition tree matching the column you want to filter, using the same field names and operators the filter menu itself produces. The grid applies it immediately and reflects the same state back in the menu if a user opens it, so a saved filter or a URL parameter can drive the table without simulating clicks. See the step-by-step how to add a set filter to a column guide for a runnable example of the checkbox-list variant and the source on GitHub.
An inline filter row beside the menu
Turn the toggle above the grid to On and a row of inline filter cells appears directly under the headings: a text box for a name column, a number box with its operator for a cost column, a date picker for a deploy date, all without opening anything. It is not a second filter system sitting beside the first one. Typing into a cell in the row and opening that same column’s menu afterwards shows the identical condition, because both write the one filter tree the grid already keeps; a condition set from the menu shows up in the row’s box the same way round. A reader who wants to narrow several columns at a glance uses the row; a reader who wants the full operator picker for one column still has the menu open at the same time.