What is CSS OKLCH
OKLCH is a color model you can use in CSS via the oklch() color function.
It’s designed to be more perceptually uniform than older formats, meaning that when you change the numbers,
the visual change is more consistent and predictable.
In other words: it tries very hard to make “small code tweaks” feel like “small visual tweaks”.
In CSS, you’ll most often use OKLCH the same way you use other colors: with properties like
color, background, border-color, box-shadow, and gradients.
The “OKLCH property” people talk about is usually just shorthand for “using OKLCH values in color-related properties”.
The actual CSS feature is the oklch() function.
.demo {
background: oklch(72% 0.18 250);
}
.demo {
background: oklch(72% 0.18 130);
}
.demo {
background: oklch(72% 0.06 250);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 13px;
display: grid;
gap: 14px;
font-family: system-ui, Arial, sans-serif;
}
.demo {
border: 3px solid #111;
border-radius: 18px;
padding: 18px;
box-shadow: 0 12px 0 #111;
display: grid;
gap: 10px;
align-content: start;
}
.demo h3 {
margin: 0;
font-size: 18px;
}
.demo p {
margin: 0;
max-width: 62ch;
line-height: 1.4;
}
.pill-row {
display: flex;
flex-wrap: wrap;
gap: 10px;
margin-top: 6px;
}
.pill {
background: #fff;
border: 2px solid #111;
border-radius: 999px;
padding: 8px 12px;
font-size: 13px;
}
.code {
background: rgba(255, 255, 255, 0.75);
border: 2px solid rgba(0, 0, 0, 0.35);
border-radius: 12px;
padding: 10px 12px;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 13px;
line-height: 1.35;
}
OKLCH as a “normal CSS color”
Click the snippets above. We keep the same “lightness” value, then change hue or chroma to see how it affects the result.
background border-color text color shadowoklch(L C H)L = lightnessC = chromaH = hue
CSS OKLCH function syntax
The basic syntax looks like this:
-
oklch(L C H) -
oklch(L C H / A)(same thing, with alpha)
Where:
-
L (Lightness): usually a percentage like
70%. Think “how bright”. -
C (Chroma): a number like
0.12. Think “how colorful / intense”. -
H (Hue): an angle like
210(degrees). Think “which color family”.
It’s very similar to HSL in spirit (lightness + colorfulness + hue), but the underlying math is different and the results tend to be more consistent when you tweak values.
Interactive OKLCH sliders
Let’s make OKLCH feel less abstract by giving you three sliders (Lightness, Chroma, Hue). Move them and watch how the color changes.
.swatch {
--l: 72;
--c: 0.16;
--h: 250;
background: oklch(calc(var(--l) * 1%) var(--c) var(--h));
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
display: grid;
gap: 14px;
font-family: system-ui, Arial, sans-serif;
}
.grid {
display: grid;
gap: 12px;
}
.swatch {
border: 3px solid #111;
border-radius: 18px;
min-height: 220px;
box-shadow: 0 12px 0 #111;
display: grid;
align-content: end;
padding: 16px;
}
.panel {
background: rgba(255, 255, 255, 0.82);
border: 2px solid rgba(0, 0, 0, 0.35);
border-radius: 14px;
padding: 12px 12px;
display: grid;
gap: 6px;
}
.panel h4 {
margin: 0;
font-size: 14px;
}
.panel p {
margin: 0;
font-size: 13px;
line-height: 1.35;
max-width: 60ch;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
Your current OKLCH
oklch(L C H) where L is a percentage, C is chroma, and H is degrees.
CSS OKLCH opacity and alpha
OKLCH supports transparency the same way modern rgb() and hsl() do:
you add a slash and an alpha value:
-
oklch(70% 0.15 250 / 0.7) -
oklch(70% 0.15 250 / 70%)
That last piece (after /) is the alpha channel. It can be a number from 0 to 1,
or a percentage.
Interactive alpha slider
This playground uses one slider to change the alpha value while keeping the color constant.
Notice how the background pattern “shows through” more as alpha approaches 0.
.card {
--a: 0.85;
background: oklch(68% 0.18 25 / var(--a));
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.stage {
border: 3px solid #111;
border-radius: 18px;
box-shadow: 0 12px 0 #111;
padding: 16px;
background:
repeating-linear-gradient(
45deg,
#f2f2f2 0 12px,
#e6e6e6 12px 24px
);
display: grid;
gap: 12px;
}
.card {
border: 2px solid rgba(0, 0, 0, 0.35);
border-radius: 16px;
padding: 14px;
display: grid;
gap: 8px;
}
.card h4 {
margin: 0;
font-size: 16px;
}
.card p {
margin: 0;
line-height: 1.45;
max-width: 60ch;
}
.mono {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 13px;
background: rgba(255, 255, 255, 0.75);
border: 1px solid rgba(0, 0, 0, 0.3);
border-radius: 12px;
padding: 10px 12px;
}
OKLCH with alpha
Alpha controls transparency. 1 is fully opaque, 0 is fully transparent.
background: oklch(68% 0.18 25 / A);
CSS OKLCH vs HSL
HSL is popular because it’s easy to read: hue, saturation, lightness.
The catch is that HSL’s “lightness” and “saturation” don’t map cleanly to how humans perceive brightness and intensity.
So you can set a bunch of colors to lightness: 60% and they won’t necessarily feel equally bright.
OKLCH aims to fix that by using perceptual math (based on the OKLab color space), so that equal “L” values are much closer to equal perceived lightness.
Same “lightness” comparison
This demo shows a row of HSL colors and a row of OKLCH colors.
Both rows keep their “lightness” concept constant (HSL uses l, OKLCH uses L),
but OKLCH usually looks more even across hues.
.row-hsl .chip {
background: hsl(var(--h) 90% 60%);
}
.row-oklch .chip {
background: oklch(70% 0.17 var(--h));
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
display: grid;
gap: 14px;
font-family: system-ui, Arial, sans-serif;
}
.card {
border: 3px solid #111;
border-radius: 18px;
box-shadow: 0 12px 0 #111;
padding: 16px;
display: grid;
gap: 12px;
background: #fff;
}
.card h4 {
margin: 0;
font-size: 16px;
}
.card p {
margin: 0;
line-height: 1.45;
max-width: 70ch;
}
.rows {
display: grid;
gap: 12px;
}
.row {
display: grid;
gap: 8px;
}
.label {
font-weight: 700;
font-size: 13px;
}
.chips {
display: grid;
grid-template-columns: repeat(6, minmax(0, 1fr));
gap: 10px;
}
.chip {
--h: 0;
border: 2px solid rgba(0, 0, 0, 0.35);
border-radius: 14px;
min-height: 74px;
display: grid;
place-items: center;
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
font-size: 12px;
background: #eee;
}
.chip:nth-child(1) { --h: 0; }
.chip:nth-child(2) { --h: 60; }
.chip:nth-child(3) { --h: 120; }
.chip:nth-child(4) { --h: 180; }
.chip:nth-child(5) { --h: 240; }
.chip:nth-child(6) { --h: 300; }
HSL vs OKLCH “same lightness” vibe check
Click the snippets. Both attempt “same lightness across hues”. OKLCH typically looks more consistent.
HSL: hsl(H 90% 60%)060120180240300OKLCH: oklch(70% 0.17 H)060120180240300
CSS OKLCH gradients
Gradients are where OKLCH often feels like a “cheat code”: the in-between colors tend to look smoother and less muddy.
Modern CSS also lets you influence how colors interpolate across a gradient. The interpolation color space for color blending defaults to Oklab, which is closely related to OKLCH.
Using in oklch for interpolation
You can explicitly request interpolation in OKLCH for a gradient using syntax like:
linear-gradient(in oklch, ...).
You can also tweak hue interpolation (for example, longer hue vs shorter hue) to control the path around the color wheel.
.hero {
background: linear-gradient(
90deg,
oklch(70% 0.20 20),
oklch(70% 0.20 310)
);
}
.hero {
background: linear-gradient(
90deg in oklch,
oklch(70% 0.20 20),
oklch(70% 0.20 310)
);
}
.hero {
background: linear-gradient(
90deg in oklch longer hue,
oklch(70% 0.20 20),
oklch(70% 0.20 310)
);
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
display: grid;
gap: 14px;
font-family: system-ui, Arial, sans-serif;
}
.hero {
border: 3px solid #111;
border-radius: 18px;
box-shadow: 0 12px 0 #111;
min-height: 240px;
padding: 16px;
display: grid;
align-content: end;
overflow: hidden;
position: relative;
color: #fff;
}
.hero::before {
content: "";
position: absolute;
inset: 0;
background: url("https://picsum.photos/1200/700") center / cover no-repeat;
opacity: 0.25;
}
.hero > * {
position: relative;
}
.hero h4 {
margin: 0;
font-size: 16px;
}
.hero p {
margin: 0;
line-height: 1.45;
max-width: 68ch;
}
.note {
margin-top: 10px;
background: rgba(255, 255, 255, 0.78);
border: 2px solid rgba(0, 0, 0, 0.35);
border-radius: 14px;
padding: 10px 12px;
font-size: 13px;
color: #333;
}
OKLCH gradient interpolation
Click the snippets: default interpolation vs
in oklchvsin oklch longer hue. The hue path can noticeably change the “middle” colors.Tip: If your mid-colors look weird, try changing the hue interpolation mode.
CSS OKLCH gradient animation
Animating gradients is usually tricky because gradients are images, and “image interpolation” can get messy. A reliable approach is to animate numbers (like hue) and build the gradient from those numbers. OKLCH shines here because hue rotations often look smooth while lightness stays stable.
Animating an OKLCH gradient with hue-rotate
Animating gradients can be surprisingly annoying because gradients are treated like images.
A super reliable trick is to keep the gradient itself static, then animate a
filter: hue-rotate() on the gradient layer.
The result is a smooth “color spin” effect that works great for OKLCH gradients.
In the playground below, the gradient lives on a ::before pseudo-element (so it’s its own layer),
and we rotate its hue from 0deg to 360deg forever.
Try the second snippet as well: it uses animation-direction: alternate for a back-and-forth vibe.
.banner::before {
animation: hue-spin 4s linear infinite;
}
@keyframes hue-spin {
to {
filter: hue-rotate(360deg);
}
}
.banner::before {
animation: hue-spin 4s linear infinite;
}
@keyframes hue-spin {
to {
filter: hue-rotate(360deg);
}
}
.banner::before {
animation-direction: alternate;
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.banner {
border: 3px solid #111;
border-radius: 18px;
box-shadow: 0 12px 0 #111;
min-height: 220px;
padding: 18px;
display: grid;
align-content: end;
position: relative;
overflow: hidden;
background: #fff;
}
.banner::before {
content: "";
position: absolute;
inset: 0;
background: linear-gradient(
90deg in oklch,
oklch(72% 0.20 20),
oklch(72% 0.20 150),
oklch(72% 0.20 280)
);
}
.banner > * {
position: relative;
}
.banner h4 {
margin: 0;
font-size: 16px;
}
.banner p {
margin: 0;
line-height: 1.45;
max-width: 70ch;
}
.glass {
margin-top: 10px;
background: rgba(255, 255, 255, 0.8);
border: 2px solid rgba(0, 0, 0, 0.35);
border-radius: 14px;
padding: 10px 12px;
font-size: 13px;
}
.mono {
font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
}
Animation tip: keep the gradient on its own layer
Putting the gradient on ::before makes it easy to animate without affecting the text.
Then we simply lift the content above it with position: relative.
This pattern is also handy if you later want to add overlays, noise, or a background image under the text.
Learn more about gradients in the CSS Gradient Interactive Tutorial, and learn more about CSS animations in the CSS Animation Interactive Tutorial.
CSS OKLCH support and browser compatibility
OKLCH support is very good in modern browsers, but you should still plan for older ones.
The easiest way to stay current is to check Can I use oklch().
Here’s the practical approach:
-
Provide a safe fallback color first (like
#4f46e5orhsl(...)). -
Then override with OKLCH if supported using
@supports.
Fallback pattern with @supports
This is the “friendly and boring” pattern that works well in real projects.
.badge {
background: #4f46e5;
}
@supports (color: oklch(60% 0.15 260)) {
.badge {
background: oklch(60% 0.15 260);
}
}
*,
::before,
::after {
box-sizing: border-box;
}
.wrap {
max-width: 980px;
padding: 18px;
font-family: system-ui, Arial, sans-serif;
}
.badge {
border: 3px solid #111;
border-radius: 999px;
box-shadow: 0 10px 0 #111;
color: #fff;
padding: 14px 16px;
display: inline-flex;
gap: 10px;
align-items: center;
font-weight: 700;
}
.dot {
width: 12px;
height: 12px;
border-radius: 999px;
background: rgba(255, 255, 255, 0.85);
}
.note {
margin-top: 12px;
max-width: 72ch;
line-height: 1.45;
}
OKLCH when supportedIf OKLCH isn’t supported, you still get the fallback color. If it is supported, the OKLCH value wins.
Wrap-up
-
Use
oklch(L C H / A)when you want predictable tweaks and smoother palettes. -
Alpha works with the modern slash syntax:
/ 0.5or/ 50%. -
Gradients often look nicer, and you can even request interpolation like
in oklch. -
For real-world safety, pair OKLCH with fallbacks via
@supports.
