skip to content
menu
styles · colour

colour

Colour on our screens always means something. The canvas is dark, text comes in four steps of grey, there is one blue for actions, and there are five colours for states. Colour says what something is. It never decorates, and it never says anything on its own.

canvas

--bg
#0B0F19
Primary background, deepest canvas
--bg-2
#0D1117
Deeper recesses, terminal blocks
--surface
#11161F
Cards, panels, raised areas
--surface-2
#161B26
Hover state on surface
--border
#1F2633
Hairline borders, dividers
--border-2
#2A3142
Heavier dividers

foreground

--fg
#E6E6E6
Primary text, titles
--fg-2
#C9D1D9
Body text, the n glyph
--fg-muted
#AEB8C2
Labels, meta, captions
--fg-dim
#7B8597
Helpers, hints, off; still 4.5:1

brand

--cloud-blue
#58A6FF
The > chevron, links, primary CTA
--cloud-cyan
#00A4DC
Cursor, active stroke

signal

--signal-green
#2EA043
The raw green; components use --state-success
--signal-amber
#F2B233
The raw amber; components use --state-warning
--signal-red
#F85149
The raw red; components use --state-error

controls

--border-control
#646C7A
Boundary of an input, select, checkbox; 3:1
--focus-ring
#00A4DC
The keyboard focus outline
--surface-selected
#111B2A
A selected row or item

semantic state

--state-neutral
#AEB8C2
A plain fact: missing, queued
--state-information
#58A6FF
Under way or worth knowing
--state-success
#2EA043
Done and good; never a problem
--state-warning
#F2B233
Works, but someone should look
--state-error
#F85149
Failed or blocked; needs action
--state-neutral-bg
#161B26
Neutral tint
--state-information-bg
#15202E
Information tint
--state-success-bg
#101F1B
Success tint
--state-warning-bg
#24211A
Warning tint
--state-error-bg
#25171C
Error tint

the palette

Every colour is a design token: a named value, stored once. The components use the name, never the colour's code, so a colour can change in one place and every screen follows. The swatches above are drawn from the same tokens, so they can't drift apart from the components.

group what it is for
Canvas The page, recessed areas, panels, hover, a selected row, and the thin lines between things
Text Four greys: titles and values, body text, labels and details, and hints, from the strongest to the faintest
Brand The blue of the chevron, of links and of the emphasized action; a cyan for the cursor and the active stroke
Signal The raw green, amber and red. The components don't use them for status; they use the states below
Controls The edge that makes a control visible, and the keyboard focus outline
States The colour of a status, a message or a toast: one of five meanings

semantic state

Statuses, messages and highlights take their colour from a state, never from the name of a colour. Each state has a colour for its text and symbol, a faint tint and a border. Every component that shows a state also gives it a symbol of its own shape, so the state never rests on colour alone.

state means for example never for
neutral A plain fact, with no judgement “missing”, “queued”, “not searched yet” Something that needs attention
information Under way, or worth knowing; nothing to fix “on its way”, “syncing”, “asking again” Decoration, or “this is blue”
success Done, and good “held”, “saved”, “healthy” Anything with a problem in it, however small
warning: warning It works, but not fully; someone should look “connection firewalled”, “degraded”, “can't be checked” A failure (that is an error), or emphasis
error: error Failed or blocked; it needs action “needs attention”, “unreachable”, “couldn't load” An action that destroys something (that is the danger button)

The words decide the state, not the other way round. Write the status in the object's own words first, then ask which of the five meanings it has. A domain's whole vocabulary lives in one table; status vocabulary shows how.

using colour

  • Text has four weights. The brightest grey for titles and values, the next for body text, a muted grey for labels and details, and a dim grey for hints and the least important line. To make something quieter, make it shorter rather than dimmer.
  • On a tinted background, use the muted grey or brighter. The dim grey isn't clear enough on two of the tints.
  • Blue is the action colour. It marks the chevron, links, the emphasized button, the selected tab and the bar of a selected row. Information uses the same blue, so make information read as information, with words and a symbol, and not as a link.
  • Status goes through a component. The status, the banner, the toast, the filter tabs' counts and the data table's highlighted rows take a state, never a colour. The badge still has colour tones: use it for labels and counts, not for status.
  • Never colour alone. A state also has a symbol and words, a selected row also has a ticked checkbox, and a field with an error also shows the error in words.

contrast

Text has to stand out enough from what is behind it to be read, by everyone, including people with low vision. That difference is the contrast ratio. Every pair below is worked out from the tokens by the same code that the tests run, and a pair below its minimum fails the build. So a change to a token can't quietly break this table.

98 pairs checked, 98 pass. The tightest text pair is --fg-dim on --surface-2 at 4.63:1.

Text on every background — each cell needs 4.5:1 (WCAG 2.2 AA, any size)
text token--bg--bg-2--surface--surface-2--surface-selected
--fg 15.34, passes 15.16, passes 14.53, passes 13.80, passes 13.86, passes
--fg-2 12.41, passes 12.26, passes 11.75, passes 11.16, passes 11.21, passes
--fg-muted 9.52, passes 9.40, passes 9.01, passes 8.56, passes 8.60, passes
--fg-dim 5.15, passes 5.09, passes 4.87, passes 4.63, passes 4.65, passes
--cloud-blue 7.58, passes 7.49, passes 7.18, passes 6.82, passes 6.85, passes
--cloud-cyan 6.70, passes 6.62, passes 6.34, passes 6.02, passes 6.05, passes
--state-neutral 9.52, passes 9.40, passes 9.01, passes 8.56, passes 8.60, passes
--state-information 7.58, passes 7.49, passes 7.18, passes 6.82, passes 6.85, passes
--state-success 5.68, passes 5.61, passes 5.37, passes 5.11, passes 5.13, passes
--state-warning 10.21, passes 10.09, passes 9.66, passes 9.18, passes 9.22, passes
--state-error 5.71, passes 5.65, passes 5.41, passes 5.14, passes 5.16, passes

The other rules cover a state's text on its own tint, body text on every tint, the edges that make a control visible (WCAG 1.4.11 asks for 3 to 1), and text on a filled control. A label or an icon on a filled colour, blue, green, red or amber, is always the darkest background colour: white on these colours is too faint to read, especially from across a room.

State text on its tint — needs 4.5:1
foregroundonsampleratio
--state-neutral--state-neutral-bgsample text8.56, passes
--state-information--state-information-bgsample text6.51, passes
--state-success--state-success-bgsample text5.05, passes
--state-warning--state-warning-bgsample text8.56, passes
--state-error--state-error-bgsample text5.14, passes
Text on a state tint — needs 4.5:1
foregroundonsampleratio
--fg--state-neutral-bgsample text13.80, passes
--fg-2--state-neutral-bgsample text11.16, passes
--fg-muted--state-neutral-bgsample text8.56, passes
--fg--state-information-bgsample text13.17, passes
--fg-2--state-information-bgsample text10.65, passes
--fg-muted--state-information-bgsample text8.17, passes
--fg--state-success-bgsample text13.64, passes
--fg-2--state-success-bgsample text11.03, passes
--fg-muted--state-success-bgsample text8.46, passes
--fg--state-warning-bgsample text12.87, passes
--fg-2--state-warning-bgsample text10.41, passes
--fg-muted--state-warning-bgsample text7.98, passes
--fg--state-error-bgsample text13.81, passes
--fg-2--state-error-bgsample text11.17, passes
--fg-muted--state-error-bgsample text8.57, passes
Non-text UI — needs 3:1
foregroundonsampleratio
--border-control--bg3.62, passes
--border-control--bg-23.58, passes
--border-control--surface3.43, passes
--border-control--surface-23.26, passes
--border-control--surface-selected3.27, passes
--focus-ring--bg6.70, passes
--focus-ring--bg-26.62, passes
--focus-ring--surface6.34, passes
--focus-ring--surface-26.02, passes
--focus-ring--surface-selected6.05, passes
--cloud-blue--bg7.58, passes
--cloud-blue--bg-27.49, passes
--cloud-blue--surface7.18, passes
--cloud-blue--surface-26.82, passes
--cloud-blue--surface-selected6.85, passes
Text on the focused surface (tv) — needs 4.5:1
foregroundonsampleratio
--fg--surface-focussample text10.41, passes
--fg-2--surface-focussample text8.42, passes
--fg-muted--surface-focussample text6.46, passes
Focus ring on the focused surface (tv) — needs 3:1
foregroundonsampleratio
--focus-ring--surface-focussample text4.54, passes
Text on a fill (primary button label) — needs 4.5:1
foregroundonsampleratio
--bg--cloud-bluesample text7.58, passes
Text on a fill (danger button label on hover) — needs 4.5:1
foregroundonsampleratio
--bg--signal-ambersample text10.21, passes
Text on a fill (a label or icon on a green fill) — needs 4.5:1
foregroundonsampleratio
--bg--state-successsample text5.68, passes
Text on a fill (a label or icon on a red fill) — needs 4.5:1
foregroundonsampleratio
--bg--state-errorsample text5.71, passes
Outside the policy, on purpose
tokenswhy
--border, --border-2Decorative hairlines between regions. They never alone identify a control: inputs, selects and checkboxes use border-control.
--state-*-borderChip outlines. A chip is identified by its text and its tint, both of which are checked.
--signal-green, --signal-amber, --signal-redChecked through their aliases state-success, state-warning and state-error, which share their values.

themes

The system is dark only, and it tells the browser so, which makes the browser's own controls, scrollbars and form fields dark too. A light theme and a high-contrast theme are planned, but they aren't here yet: there is no theme switch, and nothing follows a person's light or dark preference, or forced colours, today. When forced colours are on, the browser's colours replace the palette.

do and don't

do
warning: connection firewalled
The state names the meaning: a firewalled connection works, but someone should look.
don't
connection firewalled
A colour picked for its looks says the opposite of the words.
do
warning: 2 sources are over their daily limit until midnight.
On a tint, text stays at the muted grey or brighter.
don't
2 sources are over their daily limit until midnight.
The dimmest grey on a tint falls below 4.5 to 1.
do
Last asked 3h ago
Quieter by being shorter.
don't
The catalog was last asked what the library holds three hours ago, and it answered for 240 of 345 titles
Quieter by being dimmer, for text people need to read.
  • Status vocabulary: giving a domain's states one of the five meanings.
  • Accessibility: the baseline that the contrast rules are part of.
  • Brand: the chevron and its one colour.
  • Tokens: the colours in CSS, TypeScript and Tailwind.

specs

group tokens
Canvas --bg, --bg-2, --surface, --surface-2, --surface-selected, --border, --border-2
Foreground --fg (titles and values), --fg-2 (body text), --fg-muted (labels and meta), --fg-dim (hints and helpers)
Brand --cloud-blue (the > chevron, links, the primary action), --cloud-cyan (the cursor, the active stroke)
Signal --signal-green, --signal-amber, --signal-red
Controls --border-control (a control's boundary, 3:1), --focus-ring (the keyboard focus outline)
Semantic state --state-*, --state-*-bg, --state-*-border for neutral, information, success, warning and error
  • Source. Every colour is a token in tokens/tokens.json. Components read the CSS custom properties (var(--fg-2)), never a hex value. The swatches are painted with the custom properties and labelled from the typed tokens, both generated from that file.
  • On a tint. Use --fg-muted or brighter. --fg-dim is outside the policy there: 4.32:1 on --state-warning-bg and 4.42:1 on --state-information-bg.
  • Blue. --cloud-blue marks the chevron, links, the primary button, the selected tab and a selected row's rule.
  • Status. The status, the banner, the toast, the filter tabs' counts and the data table's rowHighlight take a state; the badge takes a tone.
  • Contrast. Computed by tokens/contrast.mjs, the same function the token test runs (tokens/contrast.test.ts, part of npm test and of CI) and the token build warns with.
  • Themes. The tokens declare color-scheme: dark. Nothing responds to prefers-color-scheme or forced-colors today.