2026-10-16 · 8 min read
- css
- color
- theming
light-dark() is a color function with two arguments. The computed value is the first color when the element's used color scheme is light or unknown, and the second color when the used color scheme is dark. It does not read a theme class, and it does not read prefers-color-scheme unless color-scheme has allowed the user agent to choose.
The function is the wrong tool for a theme that is only a class on the html element. It is the right tool when the page supports light and dark schemes and the chosen scheme is the one the user agent already computed.
Opt in before you expect the second color
color-scheme starts at normal. With that value, light-dark() keeps the first argument, which is why a dark operating system can still paint the light color. Set color-scheme: light dark on :root when the page can handle both, and let the user agent select. Set color-scheme: dark or color-scheme: light when the page is forcing one scheme.
The only keyword, as in color-scheme: only light, asks the user agent not to override the page with its own forced darkening. A browser extension or a UA stylesheet that would have restyled form controls then leaves those controls alone. Use only when that is the product decision, and record it, because it also stops light-dark() from following the system.
- Put color-scheme on an element that inherits into the color.
- A descendant can set color-scheme again and flip the function.
- A custom property does not change the used scheme by itself.
- Check support. An unknown function drops the whole declaration.
Do not mix it with a class palette by accident
A common stylesheet defines :root { color-scheme: light dark } and also .dark { --bg: ... }. Inside .dark, light-dark() still follows color-scheme, which is still light dark unless the class sets it. The custom properties follow the class. The two mechanisms diverge, and a border that uses light-dark() stays on the system choice while a background that uses var(--bg) follows the class.
Pick one switch. If the class is the switch, set color-scheme inside .dark and inside the light class, or stop calling light-dark() and store both colors as custom properties. If the system is the switch, delete the class override or make the class set color-scheme and nothing else.
Sample the computed color in each scheme
DevTools can emulate prefers-color-scheme. That emulation changes light-dark() only after color-scheme allows it. Record the computed hex for the light scheme and the dark scheme on the same element. Those two hex values are the contract. The function text is the source.
If the declaration is invalid in that browser, the computed color comes from an earlier rule. Diff the stylesheet with the computed style before you change the arguments. A missing semicolon or a color the engine rejects looks the same as a scheme that never flips.
Check contrast on the hex that was painted
Contrast is a property of two opaque colors, not of the function. Flatten any alpha, copy the 6-digit hex, and measure the pair you actually ship in each scheme. A light pair that passes 4.5:1 says nothing about the dark pair.
Flashman's color tool accepts a 6-digit foreground and background, draws them, and reports the WCAG ratio using the 0.03928 channel cutoff. It does not parse light-dark(). The CSS formatter only reformats the rule. Diff the two computed hex pairs when a token rename is supposed to preserve contrast.