Design Tokens
How @infinitibit_gmbh/ui design tokens work, how to use them, and how to update them.
Design Tokens
@infinitibit_gmbh/ui ships its design decisions as tokens — colors, typography,
spacing, radius, and elevation — emitted as CSS custom properties and a typed
TypeScript object. Tokens are generated from a single source of truth; you never
hand-edit the output.
The two-tier model
Tokens come in two layers:
- Primitives — the raw scale:
palette.primary.500,font.size.body-m,space.4. These are values, not decisions about where they're used. - Semantic — role-based tokens that reference primitives:
color.action.primary.bg → {palette.primary.500},color.text.link, etc.
Every token is emitted as an --ib--prefixed CSS variable with path-based
names, and semantic variables keep their reference as a var() chain:
:root {
--ib-palette-primary-500: #3062ce; /* primitive */
--ib-color-action-primary-bg: var(--ib-palette-primary-500); /* semantic → primitive */
}
Components consume semantic (and foundation) tokens only — never primitives.
That indirection is what makes theming and dark mode cheap: a theme re-points
a handful of variables instead of restating every value. Semantic colors ship in
both light and dark — light at the theme selector, dark under .dark (see
Themes → Dark mode). Components inherit dark mode for free
because they only read --ib-color-*.
Using tokens (consuming teams)
In a product app, import one theme — it provides the full token set (see Themes):
import '@infinitibit_gmbh/theme-default/theme.css';
@infinitibit_gmbh/ui/tokens.css is the raw default token layer; import it
directly only if you're not using a theme package. Either way you then reference
the same --ib-* variables in CSS:
.cta {
background: var(--ib-color-action-primary-bg);
color: var(--ib-color-action-primary-text);
border-radius: var(--ib-radius-sm);
box-shadow: var(--ib-elevation-02);
}
Or use the typed token object (handy with class-variance-authority or inline
styles) — leaf values are the var() strings, and TokenName is the union of
every token path:
import { tokens, type TokenName } from '@infinitibit_gmbh/ui/tokens';
tokens.color.action.primary.bg; // → "var(--ib-color-action-primary-bg)"
@infinitibit_gmbh/ui/tokens also exports tokenManifest (typed metadata per token —
type, original value/reference) and TOKEN_NAMES for catalog/tooling use.
Per-theme values (e.g. the docs theme's brand color) live in theme packages (
@infinitibit_gmbh/theme-*), which override a subset of--ib-*variables at build time — not in the token source. See the Themes docs.
Updating tokens (maintainers)
Tokens are generated from DTCG JSON in packages/ui/tokens-source/:
| File | Holds |
|---|---|
primitives.tokens.json | palette colors (incl. the neutral ramp), type, space |
semantic.light.tokens.json | color.* roles → reference primitives (light values); input.hover-fill, input.stroke |
semantic.dark.tokens.json | the same roles with dark values (emitted under .dark) |
foundations.tokens.json | elevation, radius, shadow.*, z-index.* (mode-independent) |
Workflow:
- Edit the relevant source file (never
src/tokens/generated/). - Regenerate:
pnpm --filter @infinitibit_gmbh/ui tokens:generate. - Commit both the source and the regenerated output.
verify:tokens (part of pnpm gate) fails the build if generated output is
stale or hand-edited, so the source stays the single source of truth.
Two common edits:
- A brand value changed (e.g. primary 500 is a new hex) → edit the
primitive in
primitives.tokens.json. Because semantics reference it viavar(), every role that uses it updates automatically — you don't touch the semantic files. - A role should use a different value (e.g. buttons use primary 600) → edit
the reference in
semantic.light.tokens.jsonandsemantic.dark.tokens.json(each mode is authored independently).
Neutral greys reference the
palette.neutral.*ramp (50–950, plus350for an AA-safe dark tertiary), so they re-theme like any other token.
How it's built
DTCG source → Style Dictionary v5 → generated, committed artifacts under
packages/ui/src/tokens/generated/ (tokens.css, tokens.ts, token-names.ts,
manifest.ts). tsdown copies tokens.css into the build, served as
@infinitibit_gmbh/ui/tokens.css. Rationale and the full decision record:
ADR 0007.
The v1 Figma Variables REST pull (#24) will regenerate tokens-source/ directly
from Figma — same DTCG contract, automated producer.
Token categories (current)
Colors (semantic)
| Group | Tokens | Purpose |
|---|---|---|
color.action.* | primary.{bg,hover-bg,text}, secondary.*, critical.* | interactive elements |
color.surface.* | canvas, subtle, input, overlays, raised | background layers |
color.text.* | primary, secondary, tertiary, inverse, link, color.text.disabled | text roles |
color.status.* | {success,info,critical,warning}.{bg,text,border} | feedback/status |
color.border.* | primary, secondary, color.border.default, color.border.strong, color.border.subtle | border roles |
color.input.* | bg, border-default, border-focus, border-error, placeholder | form inputs |
color.nav.* | bg, item-text, item-hover-bg, item-active-bg, item-active-text | navigation |
color.overlay.* | color.overlay.backdrop | modal/drawer backdrops |
color.avatar.* | color.avatar.bg, color.avatar.text | avatar fallback |
Colors (primitives)
palette.{primary,blue,red,neutral,black,white}.* — raw scale, never used by components directly.
The neutral ramp (50–950, plus 350) backs the grey text/border/surface roles in both modes.
Foundation
| Group | Tokens | Purpose |
|---|---|---|
elevation.* | 02–05 | raw box-shadow values |
shadow.* | shadow.sm, shadow.md, shadow.lg | semantic shadow aliases → elevation.{02,03,04} |
z-index.* | z-index.dropdown (200), z-index.modal (300), z-index.tooltip (400) | stacking order (unitless integers) |
radius.* | none, xs, sm, md, lg, xl, 2xl, full | corner radii (sm/lg/xl/2xl alias space.* — Tailwind scale for now) |
Typography
font.family.base (Inter — Inter Variable, system-ui, sans-serif),
font.weight.{regular,medium,semibold,bold}, font.size.*, font.line-height.*.
Load the font in your app (e.g. @fontsource-variable/inter).
Space
space.* (Tailwind defaults for now).
Interim / deferred
Heading sizes h1–h4, heading line-heights, DTCG typography composites, elevation/01,
dark-mode shadow values, and gradients are not yet in the set (tracked for token
consolidation / #24). Dark-mode colors are shipped; only dark elevation is deferred.