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">
| Preset | Accent | Corner radius | Type | Stylesheet |
|---|---|---|---|---|
| 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…
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; }
| Scope | How | Wins when |
|---|---|---|
| Grid | .lattice { --lattice-accent: #7c3aed; } | Always: the house-palette pattern. |
| Container or page | data-theme on any ancestor | The nearest data-theme wins; a grid's own theme config writes it on the grid's own root and beats anything above it. |
| Auto / OS | data-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 elements | carryTheme() | 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.