packages/design-tokens (@datler/design-tokens) is the visual source of truth: six independent axes, each a data-* attribute on <html>, that recompose the whole UI (surfaces, ink, accent, corners, spacing, type and the chart colours) from one set of custom properties. It does not keep its own copy of the design. It takes the shared library’s token contract, @jeliroco-multimedia/blunt-tokens, and adds only what is Datler’s (Jeliroco Components); how the axes are switched at runtime is in Theming System.
The six axes
| attribute | values, default first | from |
|---|---|---|
data-mode | dark · light (a stored preference may also be system, resolved before it reaches the attribute) | the library |
data-palette | slate · warm · carbon | the library |
data-accent | indigo · teal · violet · amber · green | Datler |
data-radius | sharp · soft · round | the library |
data-density | comfortable · compact | the library |
data-font | grotesk (Space Grotesk and JetBrains Mono) · plex (IBM Plex) | the library |
Mode and palette make the surface, border and ink ramp; the rest layer on top. Dark is the default. The variables have bare semantic names (--bg, --fg, --fg-dim, --brand, --brand-ink, --brand-fg, --r-sm, --row-h, --chart-1 to --chart-8); the et in the file names is not a prefix on them. Text in the accent colour uses --brand-fg, because --brand as small text fails AA in light mode for every accent; --brand is the fill, and --brand-ink the text on it.
The files
datler-tokens.cssimports@jeliroco-multimedia/blunt-tokens/defaults.css, one complete theme at:root, and@jeliroco-multimedia/blunt-tokens/axes.css, the mode, palette, radius, density and font blocks, and writes out only the accent axis. That axis keeps Datler’s attribute,data-accent, where the library’s isdata-brand, with the library’s values; components read custom properties, never axis selectors, so the attribute’s name makes no difference to them. Taking the contract rather than keeping a copy is the point: two copies of a design drift without a sign, since a custom property that disagrees raises no error, it renders differently.datler-theme.cssimportsdatler-tokens.cssand maps the tokens onto shadcn-shaped Tailwind names with@theme inline, and redefines Tailwind’sdark:variant as&:where([data-mode="dark"], [data-mode="dark"] *), so it keys off the axis rather than a class. The one semantic remap: the design’s--brand, the action colour, becomes shadcn’s--primary, and shadcn’s own--accent, a subtle hover, becomes an elevated surface. Font utilities are not declared here: thedata-fontblocks set--font-sansand--font-monodirectly, the names Tailwind’s font utilities read.index.mjsandindex.d.tsexport the typed catalog,datlerThemeAxes(each axis’sattr,label,defaultandoptions, with an accent’s swatch),datlerThemeDefaults(the attribute set to spread onto<html>),axisValuesandMODE_VALUES, the modes the CSS has blocks for (a storedsystemis turned intodarkorlightby@jeliroco-multimedia/blunt-tokens’resolveMode). Every picker renders from the catalog, so the options cannot differ from the CSS.
Consuming them
An app’s CSS entry imports, in order:
@import "tailwindcss";
@import "@datler/design-tokens/datler-theme.css";
@import "@jeliroco-multimedia/blunt-tokens/bridge.css";
@import "@jeliroco-multimedia/blunt-ui/styles.css";The renderer (apps/renderer/src/index.css), Storybook (apps/storybook/.storybook/preview.css) and the web app (apps/web/src/index.css) each do. The bridge is the second half of the contract. The library’s components read its custom properties, which datler-theme.css satisfies, and they also emit Tailwind utility classes of their own names (text-foreground-dim, border-line, bg-surface-higher); a utility exists only where a --color-* theme key declares it, so without bridge.css every one of them would compile to nothing, silently and in the direction that looks fine: missing text colour inherits, and a missing border-color falls back to currentColor. The bridge maps those names onto Datler’s token values. Because dark: is a @custom-variant, a stylesheet that re-declares it after this import would win, so nothing does.
apps/renderer/index.html carries all six defaults on <html> inline, so the first paint is already themed and a saved theme lands without a flash.
How the join is checked
What has to be verified is the join between the library’s contract and Datler’s build, and only this repository holds both halves: the library’s tests parse its own tokens and cannot see an override added here, and this repository alone cannot see what the library ships next. So the checks run here:
packages/design-tokens/contrast.test.tsparses the composed theme, the library’s defaults and axes and Datler’s accent blocks in cascade order, and holds the contrast floors for every palette, mode and accent (see Theming System);catalog.test.tsholds the catalog’s shape and defaults.- In development,
apps/renderer/src/main.tsxasks the running document what it lacks, with the library’s runtime checks:checkThemeContractfor a contract token that resolves to nothing,checkUtilityLayerfor a library utility that paints nothing (which is how a missing bridge shows), and, once the app has rendered,checkClassResolutionandfindDeadVariantsfor classes that produced no rule or variants that can never match. Each logs a count and one line per finding to the console. - A Storybook story,
apps/storybook/src/foundations/JelirocoContract.stories.tsx, lists every contract token that resolves to nothing in the live document.
Related
Theming System · Jeliroco Components · Storybook · Monorepo Layout · Architecture Overview