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
Why this is a contract
Section titled “Why this is a contract”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:
"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.
The token surface
Section titled “The token surface”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.
Namespacing
Section titled “Namespacing”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.
Categories
Section titled “Categories”| 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.
What the tokens MUST guarantee
Section titled “What the tokens MUST guarantee”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.
Versioning
Section titled “Versioning”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.
What is NOT in this contract
Section titled “What is NOT in this contract”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.
Requirements
Section titled “Requirements”| 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. |
Related
Section titled “Related”- 60-brand/ — the brand section: rules, surfaces, accessibility
- 30-repos/brand.md — the repo
- stack-manifest.md — the parallel build-time contract
- component-model — the consumer
This page lives in another repository Rendered from lemonfiber/spec at 1d10402, 2026-09-09. Read the source of this page