Dark mode started as a developer preference and became a user expectation. Shipping it as an afterthought produces interfaces that look fine in light mode and broken in dark.
Two themes, one system
Every color token needs a dark counterpart — not just background and text, but borders, shadows, focus rings, and chart colors. If your design system has 20 semantic tokens, you now have 40.
“Good design is as little design as possible.” — Dieter Rams
Dark mode violates that principle unless you treat it as a first-class palette, not an inversion filter.
Token checklist
Before you ship a toggle, every semantic token needs a pair:
- Surfaces
- Background, muted, card, popover
- Elevated layers need distinct dark values, not the same gray
- Content
- Foreground, muted-foreground, disabled text
- Links and inline code need separate contrast checks
- Chrome
- Borders, dividers, focus rings, selection highlights
- Shadows become lighter in dark mode, not darker
- Data viz
- Chart series colors desaturated for dark backgrounds
- Status colors (success, warn, error) re-tested at 4.5:1
Class-based vs media query
prefers-color-scheme respects OS settings but removes user control. A class toggle (dark on <html>) gives users a choice but requires a flash-prevention script.
@import "tailwindcss";
@variant dark (&:where(.dark, .dark *));
:root {
--background: oklch(0.98 0 0);
--foreground: oklch(0.15 0 0);
}
.dark {
--background: oklch(0.15 0 0);
--foreground: oklch(0.95 0 0);
}<script is:inline>
const stored = localStorage.getItem("theme");
const prefersDark = matchMedia("(prefers-color-scheme: dark)").matches;
if (stored === "dark" || (!stored && prefersDark)) {
document.documentElement.classList.add("dark");
}
</script>Pick one model and commit; mixing both creates bugs nobody can reproduce. Never invert with a CSS filter — that’s how you get purple links and illegible charts.
The bar
Dark mode is not inverted colors. It is a separate palette with adjusted contrast ratios, softer shadows, and desaturated accents. If you are not willing to design both, ship light mode only.