
Seamless Color Scheme Adaptations with the CSS light-dark Function
Implementing dual-mode color schemes has historically required duplicating design token declarations across @media (prefers-color-scheme: dark) media queries or managing brittle class toggles in JavaScript. Every newly introduced variable—such as background colors or text primary tokens—necessitated two separate declaration blocks, increasing stylesheet maintenance overhead and the risk of visual desynchronization. The standardized CSS light-dark() function revolutionizes this workflow by consolidating light and dark values into a single declarative function call.
The behavior of light-dark() is directly governed by the computed value of the color-scheme CSS property on the current element or its ancestors. By declaring color-scheme: light dark on the document root, the browser evaluates system preferences and automatically resolves the first parameter for light mode or the second parameter for dark mode. Furthermore, because color-scheme cascades down the DOM tree, developers can override color-scheme: dark or color-scheme: light on a specific component container, enabling localized theme isolation without overriding individual custom property tokens.
Example
:root {
/* Establish support for both modes */
color-scheme: light dark;
/* Declarative token definitions */
--bg-color: light-dark(#ffffff, #0f172a);
--text-color: light-dark(#1e293b, #f8fafc);
}
body {
background-color: var(--bg-color);
color: var(--text-color);
}
Implementation
/* Complete design system token setup using light-dark() and subtree theming */
:root {
/* Inform the browser engine that this document supports both schemes */
color-scheme: light dark;
/* Semantic surface and text color tokens */
--surface-page: light-dark(#f8fafc, #0b0f19);
--surface-card: light-dark(#ffffff, #1e293b);
--surface-card-hover: light-dark(#f1f5f9, #334155);
--text-main: light-dark(#0f172a, #f1f5f9);
--text-muted: light-dark(#64748b, #94a3b8);
--border-subtle: light-dark(#e2e8f0, #334155);
--accent-primary: light-dark(#2563eb, #3b82f6);
}
/* User manual override via data attribute or class */
:root[data-theme="light"] {
color-scheme: light;
}
:root[data-theme="dark"] {
color-scheme: dark;
}
body {
background-color: var(--surface-page);
color: var(--text-main);
font-family: system-ui, -apple-system, sans-serif;
line-height: 1.5;
margin: 0;
padding: 2rem;
}
.card {
background-color: var(--surface-card);
border: 1px solid var(--border-subtle);
border-radius: 10px;
padding: 1.5rem;
box-shadow: light-dark(0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 4px 6px -1px rgba(0, 0, 0, 0.3));
transition: background-color 150ms ease;
&:hover {
background-color: var(--surface-card-hover);
}
p {
color: var(--text-muted);
}
}
/* Subtree theme isolation: Force dark mode on an embedded code or promo panel */
.dark-panel {
color-scheme: dark;
background-color: var(--surface-card);
border: 1px solid var(--border-subtle);
padding: 1.5rem;
border-radius: 8px;
}
Main Point: The light-dark() function streamlines dual-mode styling by letting custom properties resolve automatically against the computed color-scheme of the element or document root.
Credits
Photo by Ricardo Gomez Angel on Unsplash