Dark mode

Every semantic colour token has a dark value. Components never need a dark variant: they read the tokens, and the tokens change.

How the theme is chosen

  1. If an element has data-theme="dark", it and its descendants use dark values.
  2. If an element has data-theme="light", they use light values, even when the system prefers dark.
  3. Otherwise the tokens follow prefers-color-scheme.

Because the attribute works on any element, you can render a dark panel inside a light page, or the other way round.

tsx
<section data-theme="dark">
  {/* everything in here uses dark tokens */}
</section>

Adding a theme switch

Store the user's choice, apply it to <html> before the first paint, and fall back to the system preference when nothing is stored.

html
<script>
  try {
    const theme = localStorage.getItem("theme");
    if (theme === "light" || theme === "dark") {
      document.documentElement.setAttribute("data-theme", theme);
    }
  } catch {}
</script>

The script is wrapped in try because storage can be unavailable in private windows or when site data is blocked. Place it in the document head so it runs before the body renders.

What changes in dark mode

  • Surfaces step lighter as they rise: page #0b0d12, surface #12151c, tray #161a22.
  • Shadows switch from ink tints to deeper black, and elevated surfaces gain a 1px --mrd-line ring, because shadows alone are hard to see on dark backgrounds.
  • The accent lightens to #6b8aff to keep contrast against the dark page.
  • Status tints become translucent so they sit on any surface.

Checking your own components

Test both themes whenever you add colour. A quick way is to toggle data-theme on <html> in the browser's element inspector.