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.
- 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
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 automaticallyColor 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
- Inspect the rendered UI at the breakpoints and interaction states affected by the rule.
- Confirm the computed styles match the intended fix in DevTools.
- Test at least one mobile and one desktop viewport before shipping.
- 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.