Color Mode

Relative Color Syntax

In Unit 1 you used modern color functions and color-mix(). Relative color syntax (CSS Color Module Level 5) goes further: you start from any resolved color (including var(--token)) and compute a new color by reusing or adjusting its channels in a chosen color space.

That lets you derive whole palettes (lighter surfaces, darker borders, hover states) from one brand variable while staying perceptually consistent when you use spaces like oklch.

Official reference: MDN: CSS data type <color>, relative colors.

Core Idea

Instead of hand-picking six hex codes, you express:

“Take my brand color in OKLCH, reduce lightness a bit, keep chroma and hue”
or “Take surface color and bump lightness for a hover state.”

The browser resolves the origin color, then builds a new color in the target function using channel keywords.

Syntax Pattern

General shape:


        oklch(from var(--brand) l c h);
        oklch(from var(--brand) calc(l * 0.92) c h);
        rgb(from var(--brand) r g b / 0.85);
    
Why OKLCH?

Lightness steps in oklch tend to look more even to human eyes than tweaking RGB hex by hand. For brand palettes, starting in OKLCH reduces “muddy” midtones.

Example: Brand Scale from One Variable


        :root {
            /* Base token designers can change */
            --brand: oklch(0.55 0.18 250);
        }

        .surface {
            /* Lighter background */
            background: oklch(from var(--brand) calc(l + 0.4) calc(c * 0.15) h);
            color: oklch(from var(--brand) calc(l * 0.35) c h);
        }

        .border {
            border-color: oklch(from var(--brand) calc(l * 0.75) calc(c * 0.6) h);
        }

        .accent {
            /* Saturated use of same hue family */
            background: oklch(from var(--brand) 0.62 0.2 h);
        }
    

Adjust the formulas to taste; the point is that all rows relate to --brand, so a single token change ripples through the system.

Relative Colors vs color-mix()

Both are valid; they solve slightly different problems:

You can combine them: relative syntax to get a hover color, then color-mix() to blend that with a translucent scrim.

Browser Support and Fallbacks

Relative color syntax is relatively new; support improves every release. Before relying on it in production, check caniuse: CSS relative colors and your analytics.

Progressive enhancement pattern:


        .btn {
            /* Solid fallback */
            background: #2563eb;
            background: oklch(from var(--brand, #2563eb) calc(l * 0.95) c h);
        }
    

Or define parallel custom properties in a build step or manual palette for older browsers, and override with relative colors inside @supports:


        @supports (color: rgb(from red r g b)) {
            .surface {
                background: oklch(from var(--brand) calc(l + 0.35) calc(c * 0.2) h);
            }
        }
    

Demo: Live Swatches (Supporting Browsers)

If your browser supports relative colors, the boxes below derive from a single --demo-brand value. Change the variable in DevTools to see the row update together.

base
lighter surface
darker
muted + border

Non-supporting browsers may ignore these declarations; keep fallbacks for critical contrast (text on background).

Accessibility: Contrast Still Matters

Relative math does not guarantee WCAG contrast ratios. After deriving colors, run a checker (automated in CI or manually in DevTools) especially for text vs background. If a generated pair fails, nudge l or mix with a fixed neutral using color-mix().

Do not rely on hue alone

Status and error states should not depend only on color; pair with iconography or text, as you learned in earlier units.

Summary

Relative color syntax lets you treat one or few seed tokens as inputs to a computable palette in modern color spaces. Pair it with color-mix(), custom properties, and @supports fallbacks to build theme systems that are flexible in evergreen browsers and still usable elsewhere.