Saturation and chroma

채도와 크로마

Both measure "how vivid is this color" — but HSL saturation is relative to lightness, while OKLCH chroma is an absolute measure of colorfulness.

Also known as: Saturation vs chroma컬러풀니스
···
html
<div class="wrap">
  <div class="rowlabel">HSL Saturation <span id="sVal" class="muted">S 0%</span></div>
  <div class="track" id="trackS"><div class="fill" id="fillS"></div><i id="dotS"></i></div>
  <div class="rowlabel">OKLCH Chroma <span id="cVal" class="muted">C 0.00</span></div>
  <div class="track" id="trackC"><div class="fill" id="fillC"></div><i id="dotC"></i></div>
</div>
css
.wrap{width:min(520px,92%);display:grid;gap:6px}
.rowlabel{display:flex;justify-content:space-between;align-items:baseline;font:700 clamp(10px,2.6vmin,12px)/1 ui-monospace,monospace;color:var(--fg);margin-top:10px}
.rowlabel .muted{color:var(--muted);font-weight:600}
.track{position:relative;height:clamp(20px,5.4vmin,32px);border-radius:10px;border:1px solid var(--line);cursor:pointer;touch-action:none;overflow:hidden}
.fill{position:absolute;inset:0}
.track i{position:absolute;top:50%;width:14px;height:14px;margin:-7px 0 0 -7px;border-radius:50%;background:#fff;border:2px solid var(--fg);box-shadow:0 1px 3px rgba(0,0,0,.35);pointer-events:none}
js
const hue = 250;
let t = 0, dir = 1, autoPlay = true;
const trackS = document.getElementById('trackS'), fillS = document.getElementById('fillS'), dotS = document.getElementById('dotS'), sVal = document.getElementById('sVal');
const trackC = document.getElementById('trackC'), fillC = document.getElementById('fillC'), dotC = document.getElementById('dotC'), cVal = document.getElementById('cVal');
fillS.style.background = 'linear-gradient(90deg, hsl(' + hue + ',0%,55%), hsl(' + hue + ',100%,55%))';
fillC.style.background = 'linear-gradient(90deg, oklch(62% 0 ' + hue + '), oklch(62% 0.25 ' + hue + '))';

function render(p) {
  const sat = Math.round(p * 100);
  sVal.textContent = 'S ' + sat + '%';
  dotS.style.left = (p * 100) + '%';
  const chroma = (p * 0.25);
  cVal.textContent = 'C ' + chroma.toFixed(2);
  dotC.style.left = (p * 100) + '%';
}
function bind(track, setP) {
  function fromEvent(e) {
    const r = track.getBoundingClientRect();
    const p = Math.min(1, Math.max(0, (e.clientX - r.left) / r.width));
    setP(p);
  }
  track.addEventListener('pointerdown', e => { autoPlay = false; track.setPointerCapture(e.pointerId); fromEvent(e); render(t); });
  track.addEventListener('pointermove', e => { if (e.buttons) { fromEvent(e); render(t); } });
}
bind(trackS, p => t = p);
bind(trackC, p => t = p);

(function tick() {
  if (autoPlay) {
    t += dir * 0.006;
    if (t > 1) { t = 1; dir = -1; }
    if (t < 0) { t = 0; dir = 1; }
  }
  render(t);
  requestAnimationFrame(tick);
})();

HSL saturation (S) is a clean 0–100% scale, but it's entangled with lightness — S=100% at L=10% (near-black) and S=100% at L=90% (near-white) look nowhere near equally "vivid."

Chroma — the axis OKLCH and LCH use — is defined independently of lightness, as an absolute measure of colorfulness. Holding chroma constant while sweeping lightness keeps the perceived vividness far more consistent than holding saturation constant does.

For design tokens where you want a brand color at several lightness steps, chroma-based OKLCH is the better tool; for a quick, intuitive 0–100% slider, HSL saturation still wins on simplicity.

When to use

If you need vividness to stay consistent across lightness steps, anchor to chroma instead of saturation.