Gå til hovedinnhold

Brand tokens and the documentation palette

Two brands ship from this repo, and each one lives in exactly one file under packages/ui/src/styles/:

FileBrandWho imports it
kapi-colors.csskapi teal paletteKapi Desktop, the Kapi Storybook, the kapi documentation site
theme-colors.cssRainlightThe bowrain app shells, landing page and documentation site
semantic-colors.cssNeitherImported by both, and by nothing on its own

The kapi palette uses teal accents that echo the existing neokapi logo in web/static/img/logo.png and hero-logo.png, with neutral surfaces. The light page is white (#ffffff) with deep teal primary actions (#0f716d) and charcoal text (#242526). Dark mode pairs charcoal (#1b1b1d) with lighter teal actions (#78c9c5) and dark button text (#172322). These sRGB examples are derived from the canonical OKLCH tokens, not separately maintained values.

Primary-button text contrast is 5.83:1 in light mode and 8.43:1 in dark mode. Body, muted, secondary and accent text pairs also clear 4.5:1. The documentation bridge independently verifies its text and diagram roles against page, card and code backgrounds. Bowrain keeps its Rainlight palette; shared judgement and coordinate colors retain their meaning.

semantic-colors.css holds the tokens that say what a colour means rather than what a product looks like: the judgement colours (--success, --warning, --info) and one hue per coordinate axis. An approve button is the same green on every surface, so those values sit above the brand line. See packages/ui/docs/judgement-colours.md.

Each brand file also declares its faces, --brand-font-sans, --brand-font-mono and, where the brand has one, --brand-font-display. An app's @theme inline block maps Tailwind's --font-* onto them rather than repeating the stack.

The bridge

Docusaurus paints from Infima's --ifm-* variables and the diagram kit from its own --kdx-* ones. Neither reads OKLCH, so packages/docs-palette computes an sRGB bridge from the brand tokens and commits it:

Generated fileComputed from
web/src/css/palette.generated.csskapi-colors.css
packages/docs-shared/src/diagram/palette.generated.csskapi-colors.css
bowrain/web/docs/src/css/palette.generated.csstheme-colors.css

Each site's custom.css imports its generated file and holds only the rules that are not colours. The diagram kit's file is its built-in default, which is also what a Storybook story renders; a site shipping a different brand declares the same --kdx-* properties at a higher specificity, so the drawing follows the page it sits on whatever order the stylesheets load in.

make generate-docs-palette rewrites all three. make check-docs-palette regenerates in memory and fails on a stale file; it runs in make lint, in make pre-push, and in the Reference Data Drift Gate workflow.

What is computed, and how

Surfaces cross over directly: the page, the card, the border, the band behind inline code. Three things are derived.

The primary ramp. Infima wants seven steps, --ifm-color-primary plus a dark/darker/darkest and light/lighter/lightest around it. The base is the brand primary moved along its own lightness axis until it clears WCAG AA against the tightest of the three grounds it can land on, and the six steps are fixed multiples of that lightness. A brand primary chosen for a button on a card is usually too light to be a link on a page, which is what the search corrects.

The diagram roles. --kdx-io takes the brand primary; the other five take a hue each from the semantic tokens, so a role means the same thing on both sites.

RoleHue fromReads as
iothe brand primaryreaders and writers
annotate--axis-brandannotators and overlays
translate--axis-languagetranslators and targets
check--warningchecks and enforcement
resource--successthe content memory and terms
plugin--axis-productthe plugin system

All six sit at one lightness and chroma per theme, so a diagram drawing several of them reads as one picture.

The faces. --ifm-font-family-base and --ifm-font-family-monospace come from the brand's --brand-font-*, and the diagram kit's --kdx-mono from the same monospace stack.

What the tests assert

packages/docs-palette carries its own suite, run against the real palettes:

  • Every step of the primary ramp is lighter than the one below it.
  • Links, body text, headings, muted text and every diagram accent clear WCAG AA (4.5:1) against the page, the card and the code band, in both themes.
  • Each committed file matches what its brand tokens render to, and a second render is byte-identical to the first.
  • The output carries no timestamp and no path from outside the repo, and the kapi site's file never names bowrain, which make check-docs-bowrain-clean sweeps for.

A generated file is a tracked file, so the repo formatter reads it like any other. The renderer wraps a long declaration the way the formatter would, which keeps the tree a fixed point of both make check-fmt-fixed-point and the drift gate.