Skip to main content
Beta: Front-End Checklist is currently in beta. Some issues are still being fixed. Thanks for your patience.
CSSMedium

Support dark mode with prefers-color-scheme

Implement dark mode using the prefers-color-scheme media query and CSS custom properties so the site automatically adapts to the user's system preference.

Utilities
Quick take
Typical fix time 25 min
  • Use @media (prefers-color-scheme: dark) to apply dark mode styles
  • Define light/dark colors as CSS custom properties on :root for easy switching
  • Support a manual toggle by storing preference in localStorage and setting a data-theme attribute
  • Ensure dark mode colors maintain WCAG contrast ratios
Why it matters: More than half of users prefer dark mode for reduced eye strain, especially in low-light environments. Users with photosensitivity rely on it. Implementing dark mode via prefers-color-scheme respects the user's OS preference without them needing to find a toggle, and CSS custom properties make it a clean, maintainable addition rather than a disruptive refactor.

Rule Details

Dark mode is best implemented by redefining CSS custom property values in a media query or a [data-theme] selector, without changing any component styles.

Code Example

/* ✅ Define semantic color tokens — not "dark-blue" but "color-primary" */
:root {
  /* Light mode values (default) */
  --color-surface: #ffffff;
  --color-surface-elevated: #f9fafb;
  --color-text: #111827;
  --color-text-muted: #6b7280;
  --color-border: #e5e7eb;
  --color-primary: #2563eb;
  --color-primary-foreground: #ffffff;
  --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.05);
}
 
/* Dark mode: just redefine the same variables */
@media (prefers-color-scheme: dark) {
  :root {
    --color-surface: #0f172a;
    --color-surface-elevated: #1e293b;
    --color-text: #f1f5f9;
    --color-text-muted: #94a3b8;
    --color-border: #334155;
    --color-primary: #3b82f6;
    --color-primary-foreground: #ffffff;
    --shadow-sm: 0 1px 2px rgb(0 0 0 / 0.3);
  }
}
 
/* Components use variables — no dark-mode-specific component CSS needed */
.card {
  background: var(--color-surface-elevated);
  border: 1px solid var(--color-border);
  color: var(--color-text);
  box-shadow: var(--shadow-sm);
}

Why It Matters

More than half of users prefer dark mode for reduced eye strain, especially in low-light environments. Users with photosensitivity rely on it. Implementing dark mode via prefers-color-scheme respects the user's OS preference without them needing to find a toggle, and CSS custom properties make it a clean, maintainable addition rather than a disruptive refactor.

Supporting a Manual Toggle

/* Allow data-theme attribute to override OS preference */
[data-theme="light"] {
  --color-surface: #ffffff;
  --color-text: #111827;
  /* ... all light values */
}
 
[data-theme="dark"] {
  --color-surface: #0f172a;
  --color-text: #f1f5f9;
  /* ... all dark values */
  color-scheme: dark; /* Tell browser to use dark scrollbars, inputs, etc */
}
// Manual toggle with localStorage persistence
function setTheme(theme) {
  document.documentElement.setAttribute('data-theme', theme)
  localStorage.setItem('theme-preference', theme)
}
 
// On load — respect saved preference or OS default
const saved = localStorage.getItem('theme-preference')
if (saved) {
  document.documentElement.setAttribute('data-theme', saved)
}
// If no saved preference, the media query handles it automatically

Color Scheme for Native Elements

:root {
  color-scheme: light dark; /* Browser adjusts scrollbars, form inputs, etc */
}
 
[data-theme="dark"] {
  color-scheme: dark;
}

Images in Dark Mode

/* Reduce image brightness in dark mode (useful for screenshots and diagrams) */
@media (prefers-color-scheme: dark) {
  img:not([src*=".svg"]) {
    filter: brightness(0.85) contrast(1.05);
  }
}

Smooth Transitions

/* Add a smooth transition when switching themes */
:root {
  transition: background-color 200ms ease, color 200ms ease, border-color 200ms ease;
}
 
/* But skip transition on page load */
.no-transition * {
  transition: none !important;
}

Verification

  1. Inspect the rendered UI at the breakpoints and interaction states affected by the rule.
  2. Confirm the computed styles match the intended fix in DevTools.
  3. Test at least one mobile and one desktop viewport before shipping.
  4. If the rule affects motion, contrast, or layout stability, verify those user-facing outcomes directly.

Use with AI

Copy these prompts to use with your AI assistant, or install the MCP server to use directly from Claude, Cursor, or Windsurf.

Check

Verify implementation

Check if this CSS supports dark mode. Look for prefers-color-scheme usage or a data-theme attribute pattern. Identify any hard-coded colors that would look wrong in dark mode.

Fix

Auto-fix issues

Implement dark mode by extracting colors to CSS custom properties and redefining them inside a prefers-color-scheme: dark media query.

Explain

Learn more

Explain how to implement dark mode with CSS custom properties, the prefers-color-scheme media query, and a JavaScript toggle for user preference.

Review

Code review

Review stylesheets, component styles, and responsive states related to Support dark mode with prefers-color-scheme. Flag exact selectors, declarations, or breakpoints that violate the rule in the rendered UI.

Sources

References used to support the guidance in this rule.

Further Reading

Tools and supplementary material for exploring the topic in more depth.

MDN prefers-color-schemedeveloper.mozilla.orgTool

Rules that often go hand-in-hand with this one.

Use CSS custom properties for design tokens

Define design system values (colors, spacing, typography) as CSS custom properties on :root to enable consistent theming, dynamic updates, and dark mode support.

CSS
Use transform and opacity for animations

Animate with CSS transform and opacity properties to keep animations running on the GPU compositor thread at 60fps, avoiding layout-triggering properties like top, left, width, and height.

CSS
Use oklch() and oklab() for perceptually uniform colour palettes

Colour values in the design system use oklch() or oklab() colour functions to produce perceptually uniform palettes where equal numeric steps produce equal perceived lightness changes.

CSS

Was this rule helpful?

Your feedback helps improve rule quality. This stays internal for now.

Loading feedback...
0 / 385