CSS Light-Dark Function: Adaptive Colors Without Media Query Duplication

The CSS light-dark() function (Chrome 123+, Firefox 120+, Safari 17.5+) provides a shorthand for declaring two color values — one for light mode and one for dark mode — without duplicating the property inside a @media (prefers-color-scheme: dark) block. It works alongside color-scheme and integrates cleanly with CSS custom properties for full theme systems.

Problem: A WordPress theme implements a dark mode toggle using a prefers-color-scheme media query, but user-toggled dark mode preferences — stored in a cookie or localStorage — require duplicating colour values in JavaScript and CSS separately.

Solution: Use the CSS light-dark() function to declare both light and dark colour values in a single declaration: color: light-dark(#000, #fff). Toggle the active scheme at the :root level with color-scheme: light or color-scheme: dark from JavaScript — no CSS variable duplication is needed.


The examples below show basic light-dark() usage, a complete design-token system built on it, how to let the user toggle the scheme with a class, and integration with WordPress's theme.json duotone and color palette.


/* 1. Tell the browser this element supports both schemes */
:root {
    color-scheme: light dark;
}

/* 2. Basic light-dark() usage */
body {
    background-color: light-dark( #ffffff, #1a1a1a );
    color:            light-dark( #1a1a1a, #f0f0f0 );
}

a {
    color: light-dark( #0073aa, #7cb9e8 );
}

/* 3. Design-token system using light-dark() inside custom properties */
:root {
    --color-bg:          light-dark( #fff,     #121212 );
    --color-surface:     light-dark( #f5f5f5,  #1e1e1e );
    --color-border:      light-dark( #ddd,     #333 );
    --color-text:        light-dark( #111,     #eee );
    --color-text-muted:  light-dark( #555,     #aaa );
    --color-accent:      light-dark( #0073aa,  #7cb9e8 );
    --color-accent-hover:light-dark( #005580,  #a8d4f0 );
    --shadow:            light-dark( 0 2px 8px rgb(0 0 0/.1),
                                     0 2px 8px rgb(0 0 0/.5) );
}

.card {
    background:   var(--color-surface);
    border:       1px solid var(--color-border);
    color:        var(--color-text);
    box-shadow:   var(--shadow);
    border-radius: 8px;
    padding:      1.25rem;
}

/* 4. Allow user-controlled toggle via data attribute on  */
[data-theme="light"] { color-scheme: light; }
[data-theme="dark"]  { color-scheme: dark; }

/* light-dark() automatically respects color-scheme on the element */


NOTE: light-dark() only works when color-scheme is explicitly declared on the element or one of its ancestors — it does not automatically inherit from @media (prefers-color-scheme) alone; always pair it with color-scheme: light dark on :root or the relevant container.