Lattice Grid Buy a licence

developer guide

Theming and Design-System Presets

A grid takes on the design system around it: four ready-made presets, a live read of a MUI theme, and a written contract for every token either one sets.

Four presets, one stylesheet each

Each preset is a second stylesheet, loaded after lattice-grid.css. Nothing in it is more specific than the grid's own rules, so load order alone is what makes the preset win, and there is no class to add and no build step. It restyles the accent colour, the surfaces, the corner radius, the type and the elevation, for both the light and the dark theme, in one pass.

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.80.0/lattice-grid.min.css">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@toclocoinc/lattice-grid@1.80.0/modules/presets/material3.css">
PresetAccentCorner radiusTypeStylesheet
Material 3 #6750A4 8px Roboto modules/presets/material3.css
Bootstrap 5 #0d6efd from --bs-border-radius system font stack modules/presets/bootstrap5.css
Ant Design 5 #1677ff 6px Ant Design system stack modules/presets/antd5.css
Fluent 2 #0F6CBD 4px Segoe UI stack modules/presets/fluent2.css

The Bootstrap 5 preset is the one exception to "static values": every colour it sets reads a Bootstrap custom property live, with a fallback for when Bootstrap is not on the page, so a page already running Bootstrap gets its own palette through the preset with no further wiring rather than a copy of it that can drift.

See it live

One grid, the same rows, four stylesheets. Pick a preset to swap the second <link> and watch the grid restyle in place; the reading underneath is --lattice-accent as the browser resolves it right now.

reading the live grid…

same grid, same rows pick a preset above to restyle it
Loading a live grid…

themeFromMui: read a live MUI theme

A page already inside a Material UI application does not need a static preset guessing at its palette. themeFromMui reads a real createTheme() object, or any plain object shaped like one, and hands back the matching grid tokens: the mode, the five status colours, the surfaces, the type scale and the corner radius, both as one block of ready-to-inject CSS and as a function that sets them on an element directly. It takes no dependency on @mui/material, so calling it costs nothing to a bundle that never imports it. Meant to be layered over material3.css, which fills in the roles a MUI theme does not carry, such as a hover background or a focus-ring width.

import { themeFromMui } from '@toclocoinc/lattice-grid/modules/presets';
// or: import { themeFromMui } from '@toclocoinc/lattice-grid/modules/react';

const theme = createTheme({ palette: { mode: 'dark', primary: { main: '#6750A4' } } });
const result = themeFromMui(theme);

result.cssText;          // one ready-to-inject ".lattice { --lattice-*: …; }" rule
result.apply(gridEl);    // or set every resolved token on an element directly

// call it again, and apply() again, whenever the host theme changes

Re-exported from the React, Vue and Svelte adapters, so a page already importing one of those needs no second import. On a page migrating off the MUI X Data Grid, the React guide covers <LatticeDataGrid>, the drop-in component built for exactly that move.

The theme token contract

Every one of the grid's --lattice-* custom properties a theme or a preset can set is written down, once, rather than left to be discovered by trial and error: 171 public tokens, grouped by what they affect, each marked MUST (a preset claiming to match a design system sets this or the grid visibly clashes), MAY (tunable, with a reasonable default), or leave (internal, state-driven or accessibility-mandated, and not a preset's to override). A handful of names that share the --lattice- or --lat- prefix without qualifying, such as a bar's own fill percentage set inline per cell, are marked internal for the same reason: setting one on the grid as a whole does nothing, because the inline value on the specific cell always wins.

.lattice {
  --lattice-accent: #7c3aed;        /* the house-palette pattern: always wins */
}
/* or scope it to a container instead of the grid itself */
[data-theme] { --lattice-accent: #7c3aed; }
ScopeHowWins when
Grid.lattice { --lattice-accent: #7c3aed; }Always: the house-palette pattern.
Container or pagedata-theme on any ancestorThe nearest data-theme wins; a grid's own theme config writes it on the grid's own root and beats anything above it.
Auto / OSdata-theme="auto" (or theme: 'auto')Resolves against the operating system's own light or dark preference live, so a reader who switches their OS theme sees the grid follow without a reload.
Portalled elementscarryTheme()A filter popup, a rich tooltip or a maximised grid moved onto the document body has no themed ancestor to inherit from; carryTheme() stamps the resolved theme onto it at the moment it is placed.

Motion has its own pair of tokens, --lattice-motion-duration and --lattice-motion-easing, which every transition in the grid's built-in themes reads directly. A reader who has asked their system for reduced motion gets it honoured everywhere at once: the base stylesheet zeroes the duration token under prefers-reduced-motion: reduce, rather than leaving each part of the grid to opt out on its own.

The full contract, every token with its light, dark, high-contrast and terminal default, lives in the presets API reference. For styling with custom properties more broadly, including the four built-in themes and density, see theming and density. Writing your own adapter instead of a theme? The adapter contract is the equivalent page for that surface.