Why oklch()
is the future of
colors in CSS
Andrey Sitnik, Evil Martians
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/2583932/Iv5oal8.png)
😓
😍
rgb(186, 47, 226)
oklch(60% 0.26 317)
Front-end Principal at
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10032507/01_Evil-Martians_Logo_v2.1_RGB_on-White.png)
The author of
@sitnikcode
CSS has evolved a lot
:root {
--big-font: 18px; /* Variables */
}
.title {
&.is-center { /* Nesting */
/* Vertical centering */
display: flex;
align-items: center;
justify-content: center;
}
}
@sitnikcode
Changes are coming to colors too
.old {
background: #bd89c2;
background: hsl(295, 32%, 65%);
}
.new {
background: oklch(70% 0.1 324);
}
@sitnikcode
Why do we need changes?
@sitnikcode
P3 colors
@sitnikcode
New screens show more colors
Many new Apple devices already support P3
Change 1
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10022637/p3.png)
@sitnikcode
Design systems
Change 2
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10028327/1_Ai1zPvkVmpDnK772fth43A.png)
// old, for machine // new, for human
rgb() oklch()
#hex lch()
hwb()
@sitnikcode
CSS Color 4 added better colors
Change 3
.error {
background: oklch(from var(--accent) l c 20);
}
@sitnikcode
Native color transform in CSS Color 5
Change 4
Section 2
What is oklch()
/* No comma */
rgb(1, 2, 3) → rgb(1 2 3)
/* No special function for alpha */
rgba(1, 2, 3, 0.5) → rgb(1 2 3 / 0.5)
/* % for alpha */
rgb(1 2 3 / 0.5) → rgb(1 2 3 / 50%)
/* The same for oklch() and all other color functions */
oklch(70% 0.1 330 / 50%)
@sitnikcode
New color syntax in CSS Color 4
@sitnikcode
OKLCH axes
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9520317/Снимок_экрана_от_2022-04-27_23-05-06.png)
Hue
Chroma
Lightness
oklch(L% C H / ALPHA)
0%
100%
0
0.4*
0°
360°
* — technically, chroma is unlimited, not sRGB, P3 or Rec2020 > 0.4
L
C
H
oklch(100% 0 0) /* white */
oklch( 0% 0 0) /* black */
oklch( 60% 0 0) /* gray */
oklch( 60% 0.2 20) /* red */
@sitnikcode
OKLCH examples
@sitnikcode
oklch(75% 0.2 320) /* light purple */
oklch(45% 0.2 320) /* dark purple */
oklch(75% 0.1 320) /* faded purple */
oklch(75% 0.2 140) /* complementary green */
@sitnikcode
More OKLCH examples
Section 3
Why not rgb() or #hex?
Copy-and-paste programming
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10028381/Captura_desde_2022-11-23_11-34-31.png)
@sitnikcode
“It’s harder
to read code than to write it”
— Joel Spolsky
@sitnikcode
Readability: which is darker?
Problem 1
#8000ff
#80ff00
@sitnikcode
vs
Which has a contrast issue?
Problem 1
button.is-main {
background: #8000ff;
color: #ffffff;
}
button.is-secondary {
background: #80ff00;
color: #ffffff;
}
@sitnikcode
vs
The answer
Problem 1
button.is-main {
background: #8000ff;
color: #ffffff;
}
button.is-secondary {
background: #80ff00;
color: #ffffff;
}
Contrast issue
@sitnikcode
Compare with oklch()
Problem 1
button.is-main {
background: oklch(53% 0.29 294);
color: oklch(100% 0 0);
}
button.is-main {
background: #8000ff;
color: #ffffff;
}
Contrast issue
button.is-secondary {
background: #80ff00;
color: #ffffff;
}
button.is-secondary {
background: oklch(89% 0.26 136);
color: oklch(100% 0 0);
}
@sitnikcode
No random color picking anymore
Problem 2
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030277/TKM-Header-01.jpg)
Palette generation in design systems
Problem 2
--red-${i}: oklch(${60 * i / 100} ${0.3 * i / 100} 20)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030332/Captura_desde_2022-11-23_17-59-50.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030337/Captura_desde_2022-11-23_18-00-31.png)
Section 4
Why not hsl()?
Are hsl() and oklch() similar?
hsl()
Hue
Saturation
Lightness
oklch()
Lightness
Chroma
Hue
?
@sitnikcode
Which has a contrast issue?
button.is-main {
background: hsl(270 100% 50%);
color: hsl(0 0 100%);
}
button.is-secondary {
background: hsl(90 100% 50%);
color: hsl(0 0 100%);
}
@sitnikcode
vs
The answer
button.is-main {
background: hsl(270 100% 50%);
color: hsl(0 0 100%);
}
button.is-secondary {
background: hsl(90 100% 50%);
color: hsl(0 0 100%);
}
button.is-main {
background: oklch(53% 0.29 294);
color: oklch(100% 0 0);
}
Contrast issue
button.is-secondary {
background: oklch(89% 0.26 136);
color: oklch(100% 0 0);
}
@sitnikcode
The problem
hsl()
Hue
Saturation
Lie
@sitnikcode
The problem is not with a single pair
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10029539/hsl-bw.png)
@sitnikcode
Section 5
Why is hsl() so bad?
@sitnikcode
HSL is a cylindrical color model
Hue
Lightness
Every hue has the same steps of lightness levels
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10022600/hsl-pure.jpg)
@sitnikcode
Eye color sensitivity is complex
Image by Wikipedia
@sitnikcode
OKLCH is more accurate
Every hue has different steps of lightness
Hue
Lightness
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10022603/oklch-pure.jpg)
HSL deforms space
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10022603/oklch-pure.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10022600/hsl-pure.jpg)
How human eye sees colors
HSL deforms it to rectangle
HSL deforms artifacts visible in b/w
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10022603/oklch-pure.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10022600/hsl-pure.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030359/hsl-bw.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030367/oklch-bw.png)
Section 6
Native color transformation
hsl(from hsl( 5 6 7) h s l )
@sitnikcode
Color modification in CSS Colors 5
Just an example. Don’t use with hsl() in real projects.
/* H S L */
hsl(from hsl( 5 6 7) 15 16 17) → hsl(15 16 17)
hsl(from hsl( 5 6 7) h s 17) → hsl( 5 6 17)
hsl(from hsl( 5 6 7) h s calc(l + 10)) → hsl( 5 6 17)
@sitnikcode
How relative colors will work
Just an example. Don’t use with hsl() in real projects.
@sitnikcode
Just an example. Don’t use with hsl() in real projects.
/* A little lighter */
hsl(from var(--accent) h s calc(l + 10%))
/* Error from accent */
hsl(from var(--accent) 10 s l)
Better with Custom Properties
hsl(from var(--accent) h s calc(l - 10%))
vs
darken($accent, 10%)
@sitnikcode
Why not darken() from Sass?
darken(#7aae73, 40%)
darken(#40b1b2, 40%)
@sitnikcode
Same
↓ darker
↑ lighter
Because darken() uses HSL
In Native color transforms,
we can choose the color model
/* Bad: unpredictable */
hsl(from var(--accent) h s calc(l + 10%))
/* Good */
oklch(from var(--accent) calc(l + 10%) c h)
@sitnikcode
Use oklch() now…
button {
background: oklch(53% 0.29 294);
}
button:hover {
background: oklch(63% 0.29 294);
}
@sitnikcode
… to be familiar with oklch()
for native color transforms
button {
--color: oklch(53% 0.29 294);
background: var(--color);
}
button:hover {
background: oklch(from var(--color) calc(l + 10%) c h);
}
@sitnikcode
Real magic with Custom Properties
button {
background: var(--color);
border: oklch(from var(--color) calc(l - 20%) c h);
}
button:hover {
background: oklch(from var(--color) calc(l + 10%) c h);
}
button { --color: var(--accent) }
button[disabled] { --color: var(--gray) }
button.is-delete { --color: var(--error) }
Button
Button
Button
@sitnikcode
Section 7
Wide-gamut P3 colors
Why does the sunset look different
if we have 16.7 million colors?
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030760/15831521_2.jpeg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030764/122734.jpg)
@sitnikcode
@sitnikcode
Screens show only 36% of all colors
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030783/03_sRGB.jpg)
All colors visible
by human
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9515764/2d-srgb-applep3.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9519840/image-119456--4587306.jpg)
Modern screens show +25% colors
@sitnikcode
P3 is new retina: but how to use it?
button {
background: #16f560;
}
@media (color-gamut: p3) {
button {
background: /* ? */;
}
}
hex
rgb()
hsl()
P3 support
@sitnikcode
oklch() supports P3 colors
button {
background: oklch(85% 0.25 147);
}
@media (color-gamut: p3) {
button {
background: oklch(85% 0.35 147);
}
}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030814/Снимок_экрана_2022-11-23_в_21.13.43.png)
On non-P3 screens, the example will show only an emulation of the difference
@sitnikcode
oklch() supports beyond P3 colors
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030816/Captura_desde_2022-11-23_21-18-37.png)
sRGB
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9519812/697px-Computer_monitor.jpg)
P3
Rec2020
Far future
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9519840/image-119456--4587306.jpg)
Chroma →
Section 8
oklch() problems
Is oklch() perfect?
CSS native support
The best for color transformation
Wide-gamut P3 colors
@sitnikcode
Not every oklch() is in sRGB
Problem 1
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030890/Captura_desde_2022-11-23_22-18-56.png)
@sitnikcode
But browsers find the closest color
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10030890/Captura_desde_2022-11-23_22-18-56.png)
No hue changes
@sitnikcode
Ecosystem is still growing
Problem 2
OKLCH color space was created
2020
oklch() was added to Safari
2021
First OKLCH color picker
2022
Figma support
?
@sitnikcode
But ecosystem is growing fast
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10606902/Captura_desde_2023-07-09_16-24-47.png)
Section 9
Adding oklch() to project
@sitnikcode
Polyfill: postcss-preset-env
.foo {
color: oklch(60% 0.13 30);
}
.foo {
color: rgb(193, 94, 80);
}
Step 1
@sitnikcode
.p3 {
color: oklch(40% 0.27 35);
}
It also supports P3 colors
.p3 {
color: rgb(131, 28, 0);
color: color(display-p3 0.49 0.11 0);
}
@sitnikcode
Convert colors in OKLCH color picker
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10032518/oklch-picker.png)
Step 2
@sitnikcode
Or by automatic script
npx convert-to-oklch ./src/**/*.css
.some {
background: #bd89c2;
}
.some {
background: oklch(70% 0.1 324);
}
@sitnikcode
Enforce oklch() by Stylelint
Step 3
// .stylelintrc
{
"rules": {
"function-disallowed-list": ["rgba", "hsla", "rgb", "hsl"],
"color-function-notation": "modern",
"color-no-hex": true
}
}
@sitnikcode
Work together with a designer
to add P3 colors
Step 4
@sitnikcode
Add P3 colors to your landing page
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10032637/Captura_desde_2022-11-24_11-42-35.png)
button {
background: oklch(85% 0.25 147);
}
@media (color-gamut: p3) {
button {
background: oklch(85% 0.35 147);
}
}
@sitnikcode
Read our article about OKLCH
Step 5
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10032709/Captura_desde_2022-11-24_11-59-59.png)
Section 10
The result
@sitnikcode
Benefit 1: Readability
.warning {
background: oklch(95% 0.1 100);
color: oklch(75% 0.15 100);
}
@sitnikcode
Warning example
×
95% − 75% = 20% lightness difference
It is not enough for a good contrast
@sitnikcode
Benefit 2: P3 ready, rich colors on Apple
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9515764/2d-srgb-applep3.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9519840/image-119456--4587306.jpg)
P3 is new retina
@sitnikcode
Benefit 3: Editable colors in code
@sitnikcode
button {
background: oklch(75% 0.2 320);
&:hover {
background: oklch(70% 0.2 320);
/* 5% darker */
}
}
@sitnikcode
Benefit 4: Analyzable
@sitnikcode
if (l > 0.5) {
return 'color: black'
} else {
return 'color: white'
}
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9520414/Снимок_экрана_от_2022-04-28_00-01-43.png)
Black text is readable
White text is readable
@sitnikcode
Benefit 5: A11y palettes generation
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9520432/Снимок_экрана_от_2022-04-28_00-20-36.png)
Huetone uses OKLCH to generate palette with predictable contrast
@sitnikcode
Benefit 6: Colors understanding
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9514424/oklch.png)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9519812/697px-Computer_monitor.jpg)
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9514423/hsl.png)
@sitnikcode
Benefit 7: Understanding designers
@sitnikcode
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/9520564/tumblr_85072c8db540abc59c2a657747bc4a96_0ba959b0_1280.jpg)
Subscribe
![](https://s3.amazonaws.com/media-p.slid.es/uploads/467124/images/10032507/01_Evil-Martians_Logo_v2.1_RGB_on-White.png)
Why OKLCH is the future of colors in CSS
By Andrey Sitnik
Why OKLCH is the future of colors in CSS
- 4,140