Home / Snippets / Color & Theming /

Auto-detect dark mode

Switch themes automatically using prefers-color-scheme and custom properties.

Light modeprefers-color-scheme: light
Dark modeprefers-color-scheme: dark
Widely Supported
colorthemingno-js

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.