OKLCH

사람이 느끼는 밝기에 맞춘 색공간. 같은 명도(L) 값이면 색상이 달라도 실제로 밝기가 비슷하게 보입니다.

다른 이름: OK Lightness Chroma HuePerceptual color space
···
html
<div class="wrap">
  <div class="rowlabel">HSL <span class="muted">L 10→90%, 채도 고정</span></div>
  <div class="strip" id="hslStrip"></div>
  <div class="rowlabel">OKLCH <span class="muted">L 20→95%, 크로마 고정</span></div>
  <div class="strip" id="oklchStrip"></div>
</div>
css
.wrap{width:min(560px,94%);display:grid;gap:6px}
.rowlabel{font:700 clamp(10px,2.6vmin,12px)/1 ui-monospace,monospace;color:var(--fg);margin-top:10px}
.rowlabel .muted{color:var(--muted);font-weight:500}
.strip{display:grid;grid-template-columns:repeat(9,1fr);height:clamp(28px,7vmin,46px);border-radius:10px;overflow:hidden;border:1px solid var(--line)}
js
const STEPS = 9;
let hue = 20;
const hslStrip = document.getElementById('hslStrip'), oklchStrip = document.getElementById('oklchStrip');
for (let i = 0; i < STEPS; i++) { hslStrip.appendChild(document.createElement('div')); oklchStrip.appendChild(document.createElement('div')); }

function render() {
  for (let i = 0; i < STEPS; i++) {
    const t = i / (STEPS - 1);
    const l1 = 10 + t * 80;
    hslStrip.children[i].style.background = 'hsl(' + hue + ', 75%, ' + l1 + '%)';
    const l2 = (20 + t * 75) / 100;
    oklchStrip.children[i].style.background = 'oklch(' + l2.toFixed(2) + ' 0.15 ' + hue + ')';
  }
}
(function tick() {
  hue = (hue + 0.25) % 360;
  render();
  requestAnimationFrame(tick);
})();

HSL의 명도는 sRGB 값을 수학적으로 평균 낸 것이라 사람이 실제로 느끼는 밝기와 어긋납니다 — 순수 노랑은 L=50%에서도 이미 밝게 느껴지고, 순수 파랑은 L=50%에서도 어둡게 느껴집니다. OKLCH는 사람의 지각을 모델링해서 만든 OKLab 색공간을 원기둥 좌표(L: 명도, C: 크로마, H: 색상)로 표현한 것이라 이 문제가 없습니다.

CSS에서 oklch(62% 0.15 250)처럼 씁니다. 같은 L값을 유지한 채 H(색상)만 돌리면, HSL로 같은 시도를 할 때보다 밝기가 훨씬 고르게 유지됩니다 — 팔레트의 여러 색이 "같은 무게감"으로 보이길 원할 때 특히 유리합니다.

또한 OKLCH는 sRGB보다 넓은 색 영역(P3 등 wide-gamut 디스플레이)까지 표현할 수 있습니다. 지원 브라우저는 2023년 이후 대부분의 모던 브라우저이지만, 구형 브라우저를 지원해야 한다면 HSL 폴백을 함께 준비하세요.

언제 쓰나

여러 색상에 걸쳐 밝기 인상을 통일해야 하는 팔레트나 디자인 토큰 시스템에 씁니다.