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

attributevalues, default firstfrom
data-modedark · light (a stored preference may also be system, resolved before it reaches the attribute)the library
data-paletteslate · warm · carbonthe library
data-accentindigo · teal · violet · amber · greenDatler
data-radiussharp · soft · roundthe library
data-densitycomfortable · compactthe library
data-fontgrotesk (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.css imports @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 is data-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.css imports datler-tokens.css and maps the tokens onto shadcn-shaped Tailwind names with @theme inline, and redefines Tailwind’s dark: 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: the data-font blocks set --font-sans and --font-mono directly, the names Tailwind’s font utilities read.
  • index.mjs and index.d.ts export the typed catalog, datlerThemeAxes (each axis’s attr, label, default and options, with an accent’s swatch), datlerThemeDefaults (the attribute set to spread onto <html>), axisValues and MODE_VALUES, the modes the CSS has blocks for (a stored system is turned into dark or light by @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.ts parses 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.ts holds the catalog’s shape and defaults.
  • In development, apps/renderer/src/main.tsx asks the running document what it lacks, with the library’s runtime checks: checkThemeContract for a contract token that resolves to nothing, checkUtilityLayer for a library utility that paints nothing (which is how a missing bridge shows), and, once the app has rendered, checkClassResolution and findDeadVariants for 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.

Theming System · Jeliroco Components · Storybook · Monorepo Layout · Architecture Overview