Lattice Grid Buy a licence

demo D56

Ranges and relative dates in htmx

Between, before, after, and "last 30 days" that stays relative

op: 'between'

This grid filters a column to values between two bounds, and offers relative date choices like 'last 30 days' that stay relative as time passes. It fits any column with a natural order, so a saved 'last 30 days' still means the last 30 days tomorrow.

Building…
Loading a live grid…

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="ranges-and-dates-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="service">Service</th>
      <th data-field="team">Team</th>
      <th data-field="tier">Tier</th>
      <th data-field="state">State</th>
      <th data-field="region">Region</th>
      <th data-field="errorRate">Error rate</th>
      <th data-field="monthlyCost">Monthly cost</th>
      <th data-field="lastDeploy">Last deploy</th>
      <th data-field="created">Created</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="1"><td>svc-00001</td><td>Search</td><td>silver</td><td>unknown</td><td>Malmö</td><td>0.025</td><td>37920.55</td><td>2026-06-21</td><td>2024-06-10</td></tr>
    <tr data-id="2"><td>svc-00002</td><td>Mobile</td><td>bronze</td><td>degraded</td><td>Leeds</td><td>0.0215</td><td>30070.04</td><td>2025-10-09</td><td>2022-01-06</td></tr>
    <tr data-id="3"><td>svc-00003</td><td>Identity</td><td>silver</td><td>degraded</td><td>Zürich</td><td>0.0168</td><td>36278.48</td><td>2026-08-16</td><td>2024-10-16</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';

  // 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('ranges-and-dates-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: { type: 'set' } },
      { field: 'tier', title: 'Tier', filter: { type: 'set' } },
      { field: 'state', title: 'State', filter: { type: 'set' } },
      { field: 'region', title: 'Region', filter: { type: 'set' } },
      { field: 'errorRate', title: 'Error rate', type: 'number', format: { style: 'percent', decimals: 2 }, filter: { type: 'number' } },
      { field: 'monthlyCost', title: 'Monthly cost', type: 'number', layout: { width: 160 },
        format: { style: 'currency', currency: 'USD' }, total: 'sum', filter: { type: 'number' } },
      { field: 'lastDeploy', title: 'Last deploy', type: 'dateString', filter: { type: 'date' } },
      { field: 'created', title: 'Created', type: 'dateString', filter: { type: 'date' } },
    ],
    // Opens already filtered: a cost band, and deploys in the last 30 days.
    // The date pair is what actually filters, so compute your own two dates
    // (n days before today, and today) when you build this in your app.
    state: {
      version: 1,
      filters: {
        op: 'and',
        conditions: [
          { col: 'monthlyCost', op: 'between', value: [5000, 20000] },
          { col: 'lastDeploy', op: 'between', value: ['2026-07-27', '2026-08-26'], bounds: '[)', meta: { relative: 'lastNDays', n: 30 } },
        ],
      },
    },
  });
</script>

Filtering a column by range, and dates that stay relative

A range filter narrows a column to values between two bounds rather than matching one exact value: a price between two figures, a score above a threshold, a date before or after a chosen day. A developer reaches for it wherever the underlying type is ordered rather than categorical, since a set filter’s checkbox list stops being useful once the distinct values run into the thousands. Lattice Grid configures this through the op: 'between' operator on a column’s filter, alongside before and after for open-ended bounds, and the same operators accept a relative date rather than a fixed one. A filter set to “last 30 days” stores that phrase, not the date it resolved to at the moment it was applied, so reopening the grid a week later still shows the trailing thirty days rather than a window that has silently drifted stale. Evaluating a range filter is a single comparison per row against the stored bounds, so it costs no more at a million rows than an equality filter and needs no secondary index over the column. The filter menu’s date and number inputs are native form controls, so keyboard entry, locale-aware parsing and screen reader labelling come from the browser rather than a custom widget the grid would otherwise have to maintain.

How do I filter a data grid column by date range or “last 30 days”?

Set the column’s filter operator to between with two bounds, or to before / after for one open end. For a rolling window, pass a relative expression such as “last 30 days” instead of fixed dates: Lattice Grid re-resolves it against the current date on every evaluation, so a saved or reloaded filter keeps showing the trailing period rather than the original date range.