Theming
A brand is a small JSON file. Everything else, light and dark, button states, dark sections and offer levels, is derived from it and checked for contrast at build time.
Make a brand
Four inputs: an accent, an ink, a paper and a body font. The brand builder does this in the browser; make-brand does it on the command line and writes packages/tokens/brands/<name>.json.
Terminal
node packages/tokens/scripts/make-brand.mjs --name acme --label "Acme Roofing" \
--accent "#0f766e" --ink "#111827" --paper "#fbfaf7" \
--font-body "'Hanken Grotesk Variable', Arial, sans-serif" \
--font-display "'Fraunces Variable', Georgia, serif"
# or: --input brief.json (same keys), --dry-run, --report report.json, --appearance solid|depthIt exits with an error and writes nothing when a required pair fails AA. --force writes the file for review, but the build still refuses it. After adding a brand, add its ./brands/<name>.css entry to the package exports and rebuild.
Brands and modes
Two attributes, on <html> or on any element: data-brand picks the palette and data-theme picks light or dark. Nesting works both ways.
HTML
<html data-brand="harbour" data-theme="light">
<section data-theme="dark">Still Harbour, now in its dark palette.</section>
</html>This site, light
This site, dark
| Brand | Modes | Default button | Stylesheet |
|---|---|---|---|
brandui (default) | light, dark, operator | solid | @brandcloud/tokens/themes.css |
docs | light, dark | depth | @brandcloud/tokens/brands/docs.css |
harbour | light, dark | depth | @brandcloud/tokens/brands/harbour.css |
marigold | light, dark | solid | @brandcloud/tokens/brands/marigold.css |
orchard | light, dark | depth | @brandcloud/tokens/brands/orchard.css |
The depth button tokens
The depth button is a tonal gradient that models light on a physical button. All of it comes from tokens, so it follows every brand.
| Role | Use |
|---|---|
action-depth-from, action-depth-to | Body gradient, lighter top to deeper bottom, same hue |
action-depth-glow, action-depth-glow-reach | Light rising inside from the bottom edge, and how far it reaches (30%: further drops the label below AA) |
action-depth-sheen | Thin white sheen over the top half |
action-depth-edge, action-depth-rim | Lit 1px top edge and the inset rim |
action-depth-text | The label |
action-depth-fill | Ready-made background-image: glow plus body |
shadow-action-depth, shadow-action-depth-hover | Contact shadow plus a same-hue glow underneath |
CSS
.button {
background-image: var(--brand-action-depth-fill);
box-shadow: var(--brand-shadow-action-depth);
color: var(--brand-action-depth-text);
}
.button:hover { box-shadow: var(--brand-shadow-action-depth-hover); }Offer level tokens
Five levels, sky, blue, navy, pearl and obsidian, for ranking offers and plans. Print the level name next to the colour so colour is never the only cue.
- Sky
level-sky-* - Blue
level-blue-* - Navy
level-navy-* - Pearl
level-pearl-* - Obsidian
level-obsidian-*
| Role (L is the level) | Use |
|---|---|
level-L-highlight | Top stop of a level button or tile |
level-L-base | Bottom stop, and the fill of a solid level button |
level-L-deep | Contact shadow and rim. Never a text background |
level-L-halo | Soft same-hue glow underneath |
level-L-text | Label on highlight, base and hover (checked on all three) |
level-L-mark | Flat identity colour for marks, dots and rules |
level-L-ink | Level-coloured text on the page |
level-L-fill | Ready-made linear-gradient(highlight, base) |
The gradient rule
Gradients are welcome when they model light on an object: one hue, lighter at the top, a thin sheen and a same-hue shadow, on buttons, level tiles and at most one brand-colour section. The build refuses any brand whose depth, level or WhatsApp stops span more than 40 degrees of hue, and the UI lint flags multi-hue gradients and gradient text in your code.
Terminal
pnpm exec brand-ui-lint # check against the baseline
pnpm exec brand-ui-lint --update-baselineColour roles for this site's brand
Every role exists in every brand and mode and compiles to --brand-<role>. These are the values for the docs brand.
Show 44 colour roles, light and dark
| Role | Light | Dark |
|---|---|---|
accent | #b8430d | #d45b2e |
accent-hover | #9e3603 | #e96e42 |
accent-subtle | #f8ece7 | #392115 |
accent-text | #ffffff | #17140f |
action-ink | #17140f | #f6f1e8 |
action-ink-hover | #33302c | #ffffff |
action-ink-text | #ffffff | #17140f |
border | rgb(23 20 15 / 0.12) | rgb(255 255 255 / 0.12) |
border-strong | rgb(23 20 15 / 0.28) | rgb(255 255 255 / 0.3) |
canvas | #f6f1e8 | #17140f |
danger | #be123c | #d56782 |
focus | #b8430d | #d45b2e |
focus-ring | rgb(184 67 13 / 0.24) | rgb(212 91 46 / 0.32) |
inverse-border | rgb(255 255 255 / 0.12) | rgb(255 255 255 / 0.14) |
inverse-canvas | #17140f | #0c0a08 |
inverse-surface | #25221d | #23201b |
inverse-surface-muted | #2e2c27 | #2e2c27 |
inverse-text | #f6f1e8 | #f6f1e8 |
inverse-text-muted | #afaaa3 | #b3afa7 |
status-danger-border | #fecdd3 | #9f1239 |
status-danger-subtle | #ffe4e6 | #4c1726 |
status-info | #0369a1 | #38bdf8 |
status-info-border | #bae6fd | #075985 |
status-info-subtle | #e0f2fe | #0c354d |
status-success | #157e3c | #4ade80 |
status-success-border | #bbf7d0 | #166534 |
status-success-subtle | #dcfce7 | #123524 |
status-warning | #9b5f07 | #fbbf24 |
status-warning-accent | #d97706 | #fbbf24 |
status-warning-border | #fde68a | #854d0e |
status-warning-subtle | #fef3c7 | #422f0a |
status-warning-text | #854d0e | #fbbf24 |
surface | #ffffff | #23201b |
surface-interactive | #e6e2d9 | #2e2c27 |
surface-muted | #ebe6dd | #23201b |
surface-raised | #ffffff | #2a2722 |
surface-subtle | #f6f1e8 | #1e1b16 |
text | #17140f | #f6f1e8 |
text-muted | #5a5650 | #b3afa7 |
text-subtle | #716d67 | #98948d |
whatsapp | #1fbf5a | #1fbf5a |
whatsapp-highlight | #3ee07c | #3ee07c |
whatsapp-hover | #25d366 | #25d366 |
whatsapp-text | #062612 | #062612 |