Color

Seven semantic roles, each a ten-step ramp. Components name a role and a step, never a hex. That indirection is the whole reason a re-brand is a one-file change.

import from src/brand/brand.css

The seam

src/system/theme.css declares which roles exist. It is byte-identical with the design-system reference and travels between projects untouched. src/brand/brand.css supplies what each role resolves to, as an H S% L% triplet. The contract wraps those in hsl() and emits the utilities.

brand.css   --primary-500: 44 100% 48%            ← the value  (brand)
theme.css   --color-primary-500: hsl(var(--primary-500))  ← the role   (system)
            → Tailwind emits bg-primary-500, text-primary-500, …

Dark is the only mode

The site renders <html class="dark"> and has no toggle, so the .dark block in brand.css is the one that actually paints. Its neutral ramp is inverted: neutral-50 is the darkest surface and neutral-900 the lightest ink. That is why body text is text-neutral-800 and the page sits on bg-neutral-50. Never write a dark: variant here. There is nothing to switch to.

Roles

neutral

the backbone — pure grayscale, #000000 to #FFFFFF

50
100
200
300
400
500
600
700
800
900

primary

yellow — the signal, base #F7B500

50
100
200
300
400
500
600
700
800
900

accent

red — punctuation, base #C00B1D

50
100
200
300
400
500
600
700
800
900

info

semantic only (blue) — a functional signal, not a brand color

50
100
200
300
400
500
600
700
800
900

success

design-system default

50
100
200
300
400
500
600
700
800
900

warning

design-system default

50
100
200
300
400
500
600
700
800
900

destructive

design-system default

50
100
200
300
400
500
600
700
800
900

No literal color scales

There are deliberately no named color scales here, no bg-yellow-500 or text-cyan-400. A component names a role and a step, and nothing else. A literal scale sitting beside the roles would be a second representation of the same colors, kept in sync by hand, which is exactly the drift the two-tier model exists to prevent.

Contrast is membership, not advice

A foreground and background pair is only a legal member of the set if it clears WCAG AA. An off-contrast pair is not discouraged, it is not in the set. The one carve-out is disabled, which is contrast-exempt: the rule is that interactive and enabled pairs must clear contrast.

primary / primary-foreground

the button pair

accent / accent-foreground

the marquee pair

neutral-50 / neutral-800

body — and neutral-500 for muted

destructive / foreground

red means error, everywhere