Skip to content

Contract: design tokens

Status: Accepted

The interface between brand and lemonfiber-web. The web surface’s visual language comes entirely from these tokens; it hardcodes no colour, size, or spacing.

Satisfies: G1-R2, G3-R3, DES-R1


The web UI (component-model) is built in its own repository and embedded in the binary. If it hardcodes #F0C419, a brand change means editing Rust-adjacent frontend source in a different repo — the same coupling the stack manifest exists to avoid, in a different guise.

Tokens make the brand data the web UI consumes, exactly as the manifest makes the stack data the CLI consumes. brand owns the values; lemonfiber consumes them at build time.

Consumption: a pinned dependency, at build time

Section titled “Consumption: a pinned dependency, at build time”

brand is packaged as @lemonfiber/brand. lemonfiber-web takes it as a build dependency, pinned to a commit so a token change lands deliberately, and compiles the tokens into the assets it tags (ADR-0012), which lemonfiber then embeds:

lemonfiber-web/package.json
"dependencies": { "@lemonfiber/brand": "github:lemonfiber/brand#<sha>" }
@import "@lemonfiber/brand/tokens.css";
.header { background: var(--lf-color-paper); color: var(--lf-color-ink); }

The version is pinned. A brand release is a deliberate lemonfiber dependency bump (cite GOV-R12), never a floating pull — the same discipline as pinned image tags (E1-R1), for the same reason.

The build runs in lemonfiber-web’s own CI, so the npm toolchain stays out of the Rust workspace entirely; what lemonfiber embeds is the tagged output. The tokens reach the end user embedded, never as a runtime dependency.

Published as both tokens.css (CSS custom properties) and tokens.json (raw values). CSS for the web UI; JSON for anything that needs the values as data — notably the TUI’s colour mapping.

Every token is prefixed --lf- (CSS) or lives under a typed key (JSON). The prefix is part of the contract: it guarantees no collision with a token the web UI defines itself, and makes brand tokens greppable.

Category Prefix Example
Colour — core --lf-color-{ink,lemon,fiber,leaf,…} --lf-color-lemon: #F0C419
Colour — surfaces --lf-color-{paper,pith,canvas,line,…} --lf-color-paper: #FBF7EA
Type --lf-font-*, --lf-size-*, --lf-weight-*, --lf-tracking-* --lf-size-body: 15px
Space --lf-space-{1..8} --lf-space-4: 16px (4px base)
Radius --lf-radius-{sm,md,icon,pill} --lf-radius-md: 4px
Elevation --lf-shadow-* --lf-shadow-lift

A single attribute switches the ink (dark) theme:

[data-lf-theme="ink"] { --lf-color-paper: #17160F; … }

Surface tokens are redefined under the attribute; core tokens like --lf-color-ink are not. The web UI’s theme toggle (G3) stamps this attribute — tokens are what make theme-awareness a data change rather than a code branch.

These are contract obligations, not brand preferences — the web UI relies on them:

Rule Why
Every token exists in both tokens.css and tokens.json The TUI reads JSON; drift between the two breaks colour mapping
Token names are stable within a major version Renaming a token is a breaking change to every consumer
Removing or renaming a token bumps the major version lemonfiber pins a version; a silent removal breaks its build
Every text/surface colour pair used for body copy meets WCAG AA G3-R3 requires it; see accessibility
The ink theme redefines every surface token the paper theme defines A half-themed token renders an unreadable pairing in dark mode

The contrast obligation is the one with teeth: a token pairing that fails AA is a contract violation, caught by the token contrast check, not a matter of taste.

Tokens follow semver, independent of lemonfiber, stack_version, and schema_version (versioning):

Change Version
Add a token Minor
Change a token’s value (recolour, resize) Minor — consumers pick it up on bump
Rename or remove a token Major — breaks consumers
Change what a token means Major

lemonfiber pinning an exact version means a brand recolour reaches users only when lemonfiber deliberately bumps and rebuilds — brand and binary stay decoupled, and a brand change can never surprise a shipped binary.

The logo assets (assets/logo/*.svg) are not tokens and not part of this build-time interface in the same way. They are proprietary marks (licence), referenced where the web UI needs a logo, and governed by brand rules rather than by a token schema.

ID Requirement
ARCH-R36 The web UI MUST derive all colour, type, spacing and radius from @lemonfiber/brand tokens, hardcoding none.
ARCH-R37 lemonfiber MUST depend on an exact, pinned @lemonfiber/brand version, never a range.
ARCH-R38 Tokens MUST be published as both tokens.css and tokens.json, with identical values.
ARCH-R39 Removing or renaming a token MUST be a major version bump.
ARCH-R40 Every body-text colour pairing MUST meet WCAG AA, verified by the token contrast check.
ARCH-R41 The ink theme MUST redefine every surface token the default theme defines.

This page lives in another repository Rendered from lemonfiber/spec at 1d10402, 2026-09-09. Read the source of this page