CSS light-dark Function: Simplifying Dark Mode
The CSS light-dark() function modernizes theme
management by allowing developers to declare both light and dark color
values within a single property declaration. Traditionally, implementing
dark mode required writing repetitive media queries or maintaining
complex CSS custom variable swaps across multiple selectors. By pairing
directly with the standard color-scheme property,
light-dark() streamlines style sheets, reduces code
complexity, and makes component-level theme overrides intuitive and
maintainable.
The Limitations of Traditional Dark Mode Approaches
Historically, creating a dual-theme website required one of two patterns:
- Media Queries: Writing separate
@media (prefers-color-scheme: dark)blocks that duplicate selectors to override color properties. - Class-Based Variable Swapping: Defining design
tokens as CSS custom properties on
:root, then redefining those exact same variables under a.darkclass or inside a media query.
While functional, both approaches fragment style declarations. To see what a specific component looks like in both themes, developers had to cross-reference multiple parts of a stylesheet or maintain long lists of abstract CSS custom properties.
How light-dark() Works
The light-dark() function takes two arguments, both of
which must be valid color values:
color: light-dark(light-value, dark-value);The browser evaluates the computed value of the element's
color-scheme property to determine which color to apply. If
color-scheme resolves to light, the first
color is used. If it resolves to dark, the second color is
rendered.
To enable this behavior globally, you must declare the supported schemes on the root element:
:root {
color-scheme: light dark;
}
body {
background-color: light-dark(#ffffff, #121212);
color: light-dark(#222222, #e0e0e0);
}If the user's operating system is set to dark mode, the browser
automatically switches the active color-scheme to
dark, which immediately resolves all
light-dark() instances to their second parameter.
Granular, Component-Level Theming
A major advantage of light-dark() over traditional
methods is local encapsulation. Because the function responds directly
to the local color-scheme context, you can force a specific
component into light or dark mode simply by altering its
color-scheme property.
.card {
background-color: light-dark(#f8f9fa, #1f1f1f);
border: 1px solid light-dark(#e9ecef, #333333);
}
/* Force this specific element to always remain dark */
.card-featured {
color-scheme: dark;
}Any child elements inside .card-featured that use
light-dark() will automatically render their dark
variations without requiring specialized utility classes or overridden
custom properties.
Integration with CSS Custom Properties
The light-dark() function works seamlessly with CSS
variables. You can store your theme-reactive colors inside custom
properties to maintain a single design token architecture:
:root {
color-scheme: light dark;
--surface: light-dark(#ffffff, #18181b);
--text-primary: light-dark(#09090b, #fafafa);
--border: light-dark(#e4e4e7, #27272a);
}
.button {
background-color: var(--surface);
color: var(--text-primary);
border: 1px solid var(--border);
}This pattern eliminates the need to declare duplicate
:root blocks for different themes, consolidating token
declarations into a single, cohesive system.
Browser Compatibility
The light-dark() function is part of the Baseline modern
web features, supported across all major rendering engines including
Chromium (Chrome/Edge 123+), Gecko (Firefox 120+), and WebKit (Safari
17.5+). Adopting it reduces stylesheet file sizes, prevents
context-switching when editing components, and provides a native,
resilient standard for modern web theming.