Encodera

Thanks for reading!

Donate to the site — we work hard for you.

Contents

Modern colors

The colors lesson covered rgb() and hsl(). They have a drawback: the numbers match poorly with how the eye sees color. Modern CSS added the oklch() function, where equal numbers mean equal visible lightness, and tools to get new colors from existing ones.

The oklch() function

oklch() takes three numbers: lightness L (from 0% — black, to 100% — white), chroma C, the color intensity (from 0 — gray, to about 0.37 — the most vivid) and hue H — an angle on the color wheel from 0 to 360:

<style>
  .swatch { display: inline-block; width: 90px; padding: 24px 0; text-align: center; color: white; }
</style>
 
<div class="swatch" style="background: oklch(60% 0.2 25);">red</div>
<div class="swatch" style="background: oklch(60% 0.2 145);">green</div>
<div class="swatch" style="background: oklch(60% 0.2 260);">blue</div>

It works like hsl(), only the order is different: lightness first, hue last.

Why not hsl()

Lightness in hsl() is mathematical, not what the eye sees. At the same 50%, yellow looks almost white while blue looks dark. In oklch() equal lightness really does look equal:

<style>
  .row { display: flex; gap: 4px; margin-bottom: 4px; }
  .row div { flex: 1; padding: 12px 0; text-align: center; }
</style>
 
<div class="row">
  <div style="background: hsl(60 100% 50%);">hsl</div>
  <div style="background: hsl(120 100% 50%);">hsl</div>
  <div style="background: hsl(240 100% 50%);">hsl</div>
</div>
<div class="row">
  <div style="background: oklch(75% 0.15 100);">oklch</div>
  <div style="background: oklch(75% 0.15 145);">oklch</div>
  <div style="background: oklch(75% 0.15 260);">oklch</div>
</div>

In the top row the text on blue is barely readable; in the bottom row all three colors are equally light.

A palette from one hue

Since lightness is honest, a palette is made by changing only the first number. The hue is kept in a variable — change it, and the whole scale is recolored:

<style>
  .palette { --hue: 260; display: flex; }
  .palette div { flex: 1; height: 40px; }
</style>
 
<div class="palette">
  <div style="background: oklch(95% 0.03 var(--hue));"></div>
  <div style="background: oklch(80% 0.1 var(--hue));"></div>
  <div style="background: oklch(65% 0.15 var(--hue));"></div>
  <div style="background: oklch(50% 0.15 var(--hue));"></div>
  <div style="background: oklch(35% 0.1 var(--hue));"></div>
</div>

Mixing: color-mix()

color-mix() mixes two colors in a given proportion. The first argument says in which color space to mix — usually in oklch. This is how a hover shade is made from the brand color:

<style>
  :root { --brand: royalblue; }
  .btn { padding: 8px 16px; border: none; color: white; background: var(--brand); }
  .btn:hover { background: color-mix(in oklch, var(--brand), black 25%); }
  .soft { padding: 8px; margin-top: 8px; background: color-mix(in oklch, var(--brand) 15%, white); }
</style>
 
<button class="btn">Hover over the button</button>
<div class="soft">A pale background from the same color</div>

Relative colors

The relative syntax takes an existing color, splits it into the parts l, c and h and builds a new one. Each part can be kept or changed with calc():

<style>
  :root { --brand: oklch(55% 0.18 260); }
  .box { display: inline-block; width: 100px; padding: 16px 0; text-align: center; color: white; }
</style>
 
<div class="box" style="background: var(--brand);">main</div>
<div class="box" style="background: oklch(from var(--brand) calc(l + 0.2) c h);">lighter</div>
<div class="box" style="background: oklch(from var(--brand) l c calc(h + 180));">opposite</div>
<div class="box" style="background: oklch(from var(--brand) l c h / 40%);">transparent</div>

Change --brand — all the derived colors are recalculated. oklch() and color-mix() work in all major browsers since 2023, the relative syntax since 2024.

Common mistake

Lightness without the percent sign. In oklch() lightness can be written as a percentage or as a number from 0 to 1. The value oklch(70 0.15 250) does not mean 70% but 70 — more than one, and the color becomes white:

<style>
  div { padding: 12px; margin-bottom: 4px; border: 1px solid silver; }
</style>
 
<div style="background: oklch(70 0.15 250);">oklch(70 0.15 250) — white</div>
<div style="background: oklch(70% 0.15 250);">oklch(70% 0.15 250) — light blue</div>
<div style="background: oklch(0.7 0.15 250);">oklch(0.7 0.15 250) — light blue too</div>

Thanks for reading!

Donate to the site — we work hard for you.