Design System

Colour

Colour carries brand and meaning — never decoration. Primitive ramps hold the raw values; components only ever read the semantic layer, so the whole system can be rebranded from one file.

What is this?

A two-layer colour system. Primitives (--ds-blue-500, --ds-neutral-200) are locked raw values. Semantic tokens (--color-primary, --color-text-secondary, --color-error) map those primitives onto roles, once per theme.

When to use it

  • Any time a component needs a colour — always through a semantic token
  • Communicating state: success, warning, error and info
  • Establishing hierarchy between a primary action and everything around it

How to use it

  • Read --color-* (or its Tailwind utility, e.g. bg-surface, text-text-secondary)
  • Pair every background token with its matching -foreground token
  • Use -subtle backgrounds for status surfaces and solid ramps for status marks

What to avoid

  • Hex values, rgb(), or Tailwind palette classes such as bg-blue-600
  • Reading a primitive ramp directly from a component
  • Colour as the only signal for state — always pair it with an icon or text

Brand ramps

Centrim Blue 500 = #006DEA (locked). Yellow 500 = #F9C846 (locked) — a supporting accent, never the primary action.

--ds-blue-*

50

100

200

300

400

500

600

700

800

900

950

--ds-yellow-*

50

100

200

300

400

500

600

700

800

900

Neutrals

A slate-based ramp from neutral-0 (#FFFFFF) to neutral-950 (#020617). Text, surfaces and borders all resolve from here.

--ds-neutral-*

0

50

100

200

300

400

500

600

700

800

900

950

Status ramps

Each -500 carries white foreground text at 4.5:1 or better. Info stays visually distinct from Centrim Blue so status never reads as brand.

success-100Aa
success-500Aa
success-700Aa
warning-100Aa
warning-500Aa
warning-700Aa
error-100Aa
error-500Aa
error-700Aa
info-100Aa
info-500Aa
info-700Aa

Semantic system tokens

The only colour layer a component may read. Toggle the theme in the sidebar to verify both mappings.

Primary

The single brand action colour. One primary action per view.

--color-primary
--color-primary-hover
--color-primary-active
--color-primary-subtle
--color-primary-muted
--color-primary-foreground

Secondary

Neutral-weighted supporting actions that must not compete with primary.

--color-secondary
--color-secondary-hover
--color-secondary-active
--color-secondary-subtle
--color-secondary-foreground

Accent

Highlight and emphasis only — never a call to action.

--color-accent
--color-accent-hover
--color-accent-active
--color-accent-subtle
--color-accent-foreground

Background

The page canvas and its quiet variations.

--color-background
--color-background-subtle
--color-background-muted
--color-background-inverse

Surface

Anything that sits on the canvas: cards, menus, floating panels.

--color-surface
--color-surface-hover
--color-surface-active
--color-surface-raised
--color-surface-overlay

Text

Three readable weights plus disabled, inverse and link.

--color-text-primary
--color-text-secondary
--color-text-tertiary
--color-text-disabled
--color-text-inverse
--color-text-link

Border

Structure and separation. Focus is its own token, never reused.

--color-border-default
--color-border-subtle
--color-border-strong
--color-border-focus

Status

Meaning only. Each status has a solid, a subtle and a foreground.

--color-success
--color-success-subtle
--color-success-foreground
--color-warning
--color-warning-subtle
--color-warning-foreground
--color-error
--color-error-subtle
--color-error-foreground
--color-info
--color-info-subtle
--color-info-foreground

Role reference

What each semantic family is for, and what it must not be used for.
Token familyRoleNever
--color-primary-*The one brand action per viewLarge background fills or status meaning
--color-secondary-*Supporting actions beside a primaryThe main call to action
--color-accent-*Emphasis, highlights, chart accentsInteractive controls or buttons
--color-background-*The page canvas and quiet bandsCards or floating panels
--color-surface-*Cards, menus, floating panelsThe page canvas
--color-text-*Three readable weights plus disabled/inverse/linkDecorative tinting of body copy
--color-border-*Structure, separation and focusFilling a shape
--color-success|warning|error|infoState and meaning onlyBrand expression

Contrast rules

WCAG 2.2 AA is the floor, not the target

Body and label text meets 4.5:1 against its own background. Large display text and non-text UI boundaries (borders, focus rings, control outlines) meet 3:1.

--color-text-disabled is deliberately strengthened past the usual disabled grey so a disabled label stays legible while emphasis drops.

Do & don't

Do

  • Use bg-surface + text-text-primary for content that sits on the canvas.
  • Use the -subtle status background with its -subtle foreground for inline status.
  • Reserve the primary blue for the single most important action on the screen.
  • Verify every new pairing in both light and dark before shipping.

Don't

  • Do not introduce a new colour without adding it to the token layer first.
  • Do not tint text with brand or accent colour to make it feel designed.
  • Do not use the accent yellow for buttons — its contrast profile is for emphasis.
  • Do not invert light values to build dark mode; dark has its own designed mapping.

Why it is designed this way

Splitting primitives from semantics means a rebrand touches one mapping block instead of every component. It also makes dark mode a design decision rather than a filter: the dark theme re-points the same semantic names at different primitives, so surfaces stay layered and status colours stay distinguishable instead of washing out.