Lattice Grid Buy a licence

demo D132

prefers-reduced-motion in htmx

Every animation the grid drops when the viewer asks for less

@media (prefers-reduced-motion: reduce)

This grid drops its non-essential animation when the viewer's system asks for reduced motion. It respects a reader's stated preference automatically, so the grid stays calm for people who find movement distracting or uncomfortable.

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="reduced-motion-table" data-lattice-grid>
  <thead>
    <tr>
      <th data-field="part">What moves</th>
      <th data-field="selector">Selector</th>
      <th data-field="normally">Normally</th>
      <th data-field="reduced">Under reduce</th>
    </tr>
  </thead>
  <tbody>
    <tr data-id="1"><td>Group loading spinner</td><td>.lat-group[data-loading] .lat-expander-icon</td><td>lat-tree-spin 0.9s linear infinite</td><td>animation: none, and the icon dims to 45% instead of turning</td></tr>
    <tr data-id="2"><td>Header histogram bars</td><td>.lat-facet__bar, .lat-facet__fill</td><td>height and background-color over 120ms</td><td>transition: none, so bars jump to their new height</td></tr>
    <tr data-id="3"><td>Collaborator cursor ring</td><td>.lat-cell[data-presence]::after</td><td>opacity over 120ms</td><td>transition: none, so a peer ring appears and vanishes at once</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';

  // True when the viewer has asked for less motion, right now. Read live from
  // the OS setting rather than stored, since it is not in the row.
  const reducedMotionNow = () =>
    typeof matchMedia === 'function'
    && matchMedia('(prefers-reduced-motion: reduce)').matches;

  // 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('reduced-motion-table'), {
    autoHeight: true,
    columns: [
      { field: 'part', title: 'What moves', layout: { width: 210, pin: 'start' } },
      { field: 'selector', title: 'Selector', layout: { width: 300 } },
      { field: 'normally', title: 'Normally', layout: { width: 280 }, cell: { wrap: true } },
      { field: 'reduced', title: 'Under reduce', layout: { flex: 1, min: 260 }, cell: { wrap: true } },
      {
        id: 'now',
        title: 'Right now',
        layout: { width: 150 },
        // Depends on the viewer's OS setting, which is not in the row, so it
        // must never be materialised into the store as though it were data.
        sort: false,
        filter: false,
        value: {
          pure: false,
          deps: '*',
          compute: (_deps, ctx) => {
            if (!ctx?.data?.covered) return 'still plays';
            return reducedMotionNow() ? 'dropped' : 'plays';
          },
        },
        cell: {
          decoration: 'pill',
          variant: { map: { dropped: 'success', plays: 'info', 'still plays': 'neutral' } },
        },
      },
    ],
  });
</script>

Respecting prefers-reduced-motion in a data grid

Reduced motion support means the grid checks the operating system’s motion preference and drops non-essential animation when the viewer has asked for it. Lattice Grid does this automatically through the @media (prefers-reduced-motion: reduce) query rather than a grid-level flag, so row reordering, column resize easing, sort transitions and the hover shimmer on active cells all switch to instant state changes the moment the setting is on, with no configuration needed in the page that hosts the grid. A developer reaches for this when the grid sits inside a product that already honours the preference elsewhere, or when a user has reported nausea or disorientation from scrolling and reflow animation, which is the documented effect the media query exists to prevent under WCAG 2.3.3. Vestibular disorders are the usual driver behind the operating-system setting, and a JavaScript data grid that ignores it forces those users to either tolerate the motion or avoid the page. Because the check runs at the CSS layer rather than in per-interaction JavaScript, there is no extra render cost when the preference is off: the animation rules do not apply, and the grid’s virtual scrolling continues to recycle rows at the same rate regardless of which state the media query is in.

Does Lattice Grid support prefers-reduced-motion?

Yes. The grid listens for the @media (prefers-reduced-motion: reduce) query and removes transition and easing effects on row moves, resizes, sorts and hover states when it matches, without a separate configuration option. Turning on reduced motion at the operating-system level is enough; no grid-specific setting is required to make the change take effect.