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
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) todocument.documentElement’sdata-theme.autonever appears on the attribute; it resolves throughprefers-color-schemeandprefers-contrast: more. The Control UI resolves onlydark | light, so it never reaches the high-contrast block. color-schemecomes from the token blocks. Each block setscolor-schemeso 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 setsstyle.colorSchemeinline to the same resolved value.- Dark is the fallback. With no
data-themeattribute the:rootblock 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.
--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
- Add the property to the
:rootblock. If its value differs by theme, add it to thelightandhigh-contrastblocks too; a token missing from a block silently inherits the dark value. - Use only six-digit hex for colours the contrast test reads (
#rrggbb). - 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}. - If the token is a text or border colour that sits on another token, add an assertion to
test/brand-tokens-contrast.test.tsfor the pair. - Run the guards below, then
pnpm --dir ui testandpnpm test:web.
client.css
instead. Those values are the Control UI’s shipped brand and the parity test pins them.
Add or change a font safely
- Confirm the licence allows redistribution and record it beside
OFL.txt. - Add the woff2 (latin subset) under
packages/brand-tokens/fonts/. - Declare it in
fonts.csswith a relativeurl()andfont-display: swap. The packagefileslist already covers thefontsdirectory. - Update the tokens that name the family (
--font-body,--font-display,--mono). - 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 a200response. If the weight has no always-rendered use, also add it toREQUIRED_WARM_FONTSinapps/web/src/providers/font-warm.ts, because browsers load@font-facelazily.
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.
Related
- Web client components - how
apps/webbuilds on these tokens - Web client hosting - what operators and users see
- Control UI - the other consumer