Dark mode

Both modes are equally supported. The implementation rules that keep them in step.

Dark mode is not a variant of the brand — it is half of it. Both modes are designed, tested, and shipped together.

Implementation rules

  • Default follows prefers-color-scheme, with a manual override toggle persisted to localStorage.
  • CSS custom properties swap per theme. Components read tokens; they do not branch on the mode themselves.
  • Step 9 (solid accent) stays constant across modes. #E05232 is #E05232 everywhere.
  • No pure #000 or #fff as text. Use step 12 — #c5daf0 on dark, #142438 on light.
  • Images get a subtle overlay (black at low alpha) in dark mode, so a bright photograph does not punch a hole in a dark page.
  • Code blocks are always dark, regardless of mode. See Code.
The same card in both modes
Light mode
Body text is midnight-12; supporting copy is slate-11.
Deploy
Dark mode
Never pure white — midnight-12 from the dark scale.
Deploy

Surfaces

Elevation in dark mode is expressed by lightening the surface, not by deepening the shadow — a shadow against a near-black background is invisible.

LayerLightDark
App background#ffffffmidnight-dark-1 #0e1720
Subtle surfacemidnight-1midnight-dark-2 #131e2b
Raised surfacemidnight-2midnight-dark-3 #1a2b3e

Restating text on dark surfaces

The default heading colour is midnight-9, chosen so headings read correctly on light pages. That same value on a midnight surface is invisible — #1d3352 on #1d3352 is a contrast ratio of 1.00:1.

Any component that paints its own dark background must therefore restate its text colours. This applies to covers, dark boxes, footers, and overlays:

.surface--dark {
  --bs-body-color: #c5daf0;    // midnight-dark-12
  --bs-heading-color: #c5daf0;
  --bs-secondary-color: #97a8b8; // slate-dark-11, still AA
}
Do

Test every surface in both modes. Restate text colours on any component that sets its own dark background.

Don't

Ship a component that only works in one mode, or use pure black or white as a text colour in either.