Home / Snippets / Color & Theming /
Auto-detect dark mode
Switch themes automatically using prefers-color-scheme and custom properties.
Quick implementation
:root {
--bg: oklch(0.97 0.01 260);
--text: oklch(0.15 0.02 260);
--muted: oklch(0.45 0.02 260);
--card: oklch(1 0 0);
--border: oklch(0.85 0.01 260);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: oklch(0.13 0.02 260);
--text: oklch(0.93 0.01 260);
--muted: oklch(0.63 0.02 260);
--card: oklch(0.19 0.02 260);
--border: oklch(0.28 0.02 260);
}
}
body {
background: var(--bg);
color: var(--text);
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building a theme system.
Goal: Automatic light/dark mode using prefers-color-scheme and CSS custom properties — no JavaScript.
Technical constraints:
- Define all theme colors as custom properties on :root.
- Override them inside @media (prefers-color-scheme: dark).
- Use oklch() for all color values, not hex or rgba().
- Minimum tokens: --bg, --text, --muted, --card, --border, --accent.
- All UI references use var(--token) — never hardcoded colors.
Framework variant (pick one):
A) Vanilla CSS custom properties on :root.
B) React context — ThemeProvider that reads matchMedia and exposes tokens.
Edge cases to handle:
- Images may need different treatments (filter: invert or separate sources).
- Shadows should adjust opacity — darker in light mode, subtler in dark mode.
- Use color-scheme: light dark on :root for native form control theming.
- Test with forced-colors: active for Windows High Contrast mode.
Return CSS.
Why this matters in 2026
Dark mode is a user expectation, not a feature. prefers-color-scheme respects the OS setting with zero JavaScript. Custom properties make the switch a single set of variable overrides — every component automatically adapts.
The logic
Light-mode tokens are defined on :root as the default. The @media (prefers-color-scheme: dark) block overrides the same custom properties with dark values. Every element using var(--bg), var(--text), etc. switches instantly. No class toggling, no JavaScript, no flash of wrong theme.
Accessibility & performance
Add color-scheme: light dark to :root so native form controls (inputs, selects, scrollbars) also adapt. Test contrast ratios in both modes — WCAG AA requires 4.5:1 for body text. The media query is evaluated at parse time, so there's no flash of unstyled content if the CSS loads before first paint.