DEV Community

ivan kulkin
ivan kulkin

Posted on

Why OKLCH CSS variables need @supports for a real fallback

I added CSS export to an OKLCH palette generator this week. The obvious output looked like this:

:root {
  --brand: #5b21b6;
  --brand: oklch(43.22% 0.196 293.54);
}
Enter fullscreen mode Exit fullscreen mode

It looks like ordinary progressive enhancement: older browsers ignore the second declaration and keep the HEX value. That assumption is unreliable for custom properties.

The failure happens when the variable is used

A custom property accepts almost any token sequence. A browser can preserve oklch(...) as the value of --brand even if it cannot use that function as a color. The second declaration therefore replaces the first one.

The browser only discovers the unsupported color when the variable reaches a typed property:

.button {
  background: var(--brand);
}
Enter fullscreen mode Exit fullscreen mode

At that point the declaration can become invalid. The HEX line did not survive as a fallback because both declarations set the same untyped custom property.

Put the override behind a feature query

The exported file now separates the universal values from the modern override:

:root {
  --palette-1-shadow: #160067;
  --palette-2-base: #5b21b6;
  --palette-3-highlight: #ab7ce1;
  --palette-4-accent: #4dcd49;
}

@supports (color: oklch(50% 0 0)) {
  :root {
    --palette-1-shadow: oklch(25.12% 0.151 280.34);
    --palette-2-base: oklch(43.22% 0.196 293.54);
    --palette-3-highlight: oklch(67.4% 0.118 301.12);
    --palette-4-accent: oklch(74.82% 0.188 142.28);
  }
}
Enter fullscreen mode Exit fullscreen mode

Browsers without OKLCH support keep the sRGB values. Browsers that pass the feature query receive the perceptual values. Consumers do not need to know which path was selected:

.button {
  color: var(--palette-3-highlight);
  background: var(--palette-2-base);
  border-color: var(--palette-4-accent);
}
Enter fullscreen mode Exit fullscreen mode

The variable names include both order and role. The numeric prefix keeps them unique when a larger generated palette contains two colors with similar human-readable names.

Why export both formats?

OKLCH is useful when a palette needs controlled lightness steps. That matters in UI states and in small sprites where two colors with different RGB values can still look almost identical. HEX remains the practical compatibility layer and accurately represents the generator's sRGB-fitted output.

The generator handles the rest of the workflow in the browser: 2–9 colors, six harmony modes, sRGB gamut fitting, lightness inspection, sprite previews, and exports for Aseprite PAL, GIMP GPL, HEX, JSON, TXT, PNG, and now CSS. It requires no account.

Try the CSS export: https://oklchpalette.ru/create

Source: https://github.com/vansGAMee/OKLCH-PIXEL-PALETTE

The next candidates are Tailwind theme output, a Sass map, and a Godot resource. I would be interested to know which one would remove the most manual work from your workflow.

Top comments (0)