Skip to main content

Brand tokens

packages/brand-tokens is the single source of colour, spacing, motion and font tokens for both WednesdayAI browser apps: the operator Control UI (ui/) and the end-user web client (apps/web, see Web client hosting). It holds CSS only - no JavaScript - and is bundled into each app at build time.

Package layout

Consumers import by subpath: brand-tokens/tokens.css, brand-tokens/fonts.css. Both apps list "brand-tokens": "workspace:*" in dependencies; those apps are private and never published, so the plugin rule against workspace:* in dependencies does not apply here. The package is private and exempt from pnpm version:sync: it is not listed in scripts/sync-versions.ts or the npm publish candidate list, and its 0.0.0 version never changes (ADR 0080). If the version guard ever flags it, exempt private internal packages rather than stamping this one.

The theme contract

tokens.css declares tokens in three blocks. The selectors are part of the contract: Rules an app must follow:
  • A theme mode is not the attribute value. The web client stores a mode (auto | dark | light | high-contrast) but writes only the resolved theme (dark | light | high-contrast) to document.documentElement’s data-theme. auto never appears on the attribute; it resolves through prefers-color-scheme and prefers-contrast: more. The Control UI resolves only dark | light, so it never reaches the high-contrast block.
  • color-scheme comes from the token blocks. Each block sets color-scheme so native scroll bars and form controls follow the chosen theme rather than the operating system. Do not override it with a hard-coded value in an app stylesheet, and give any new theme block its own value. The Control UI also sets style.colorScheme inline to the same resolved value.
  • Dark is the fallback. With no data-theme attribute the :root block applies, so a page that has not resolved a theme yet renders dark rather than unstyled.

Token groups

The accent-foreground token

--accent-foreground is the text or icon colour for content that sits on an --accent fill: the Runs badge, the send button, the active theme segment. It is #ffffff in dark and light. In high contrast the accent is a light indigo (#818cf8), so white text would fall below WCAG AA and the block sets --accent-foreground: #0a0712 instead (6.69:1 on the accent).

How the apps consume it

Order matters in both apps because later rules win. apps/web/src/main.tsx:
ui/src/styles.css imports brand-tokens/fonts.css, then brand-tokens/tokens.css, then the console’s own stylesheets, and ./styles/brand-theme.css last. brand-theme.css keeps only console-only overrides and remaps; it still restates some dark and light values (identical to the package today), so a shared value that changes must be checked against it and against the console-parity test below. Font URLs in fonts.css are relative (url("./fonts/inter-400.woff2")). Each app’s Vite build hashes and bundles the files under its own base path, which is what lets the web client run under /v2/client/, /, or any gateway.webClient.basePath.
Tokens that only one app needs stay in that app. The web client keeps --personalise-subtle, --composer and --shadow-md in apps/web/src/ui/client.css. Move a token into the package only when both apps need it.

Add a token safely

  1. Add the property to the :root block. If its value differs by theme, add it to the light and high-contrast blocks too; a token missing from a block silently inherits the dark value.
  2. Use only six-digit hex for colours the contrast test reads (#rrggbb).
  3. Keep the block formatting: the test finds blocks by the literal text <selector> { and reads one --name: value; declaration per line up to the first \n}.
  4. If the token is a text or border colour that sits on another token, add an assertion to test/brand-tokens-contrast.test.ts for the pair.
  5. Run the guards below, then pnpm --dir ui test and pnpm test:web.
Do not edit a shipped dark or light value to fix a client-only look; override it in client.css instead. Those values are the Control UI’s shipped brand and the parity test pins them.

Add or change a font safely

  1. Confirm the licence allows redistribution and record it beside OFL.txt.
  2. Add the woff2 (latin subset) under packages/brand-tokens/fonts/.
  3. Declare it in fonts.css with a relative url() and font-display: swap. The package files list already covers the fonts directory.
  4. Update the tokens that name the family (--font-body, --font-display, --mono).
  5. Update the brand e2e (test/user-client-web-brand.e2e.test.ts): the font test counts the declared faces and requires each to load with a 200 response. If the weight has no always-rendered use, also add it to REQUIRED_WARM_FONTS in apps/web/src/providers/font-warm.ts, because browsers load @font-face lazily.

Guard tests

Run the contrast test on its own with pnpm exec vitest run test/brand-tokens-contrast.test.ts. The two e2e suites need pnpm build first and follow the guidance in Testing.