Skip to Content
WebCSS모던 CSS12. oklch와 color-mix()로 색 팔레트 만들기

이번 편의 결과물: 10편의 --color-* 토큰이 hex 값 대신 oklch() 값으로 교체되고, hover·비활성 톤(--color-primary-hover 등)이 손으로 고른 hex가 아니라 color-mix() 계산식으로 자동 생성된다. · 다루는 개념: hsl의 지각적 불균일 문제, oklch() 좌표 읽는 법, color-mix()로 톤 변형 생성

이 편에서 만드는 파일

web-practice/ └── portfolio/ └── css/ └── styles.css ~ (tokens 레이어의 색 토큰을 oklch + color-mix()로 교체)

개념 정리

지금까지 색 토큰은 #0969da처럼 hex로 적혀 있습니다. hex(정확히는 sRGB)는 디자이너가 직접 고른 값을 담기엔 문제가 없지만, 코드로 톤을 계산하려 하면 한계가 드러납니다. #0969da를 20퍼센트 더 밝게 만들려고 hsl()로 바꿔 밝기(lightness)만 올려도, 색상에 따라 사람 눈에는 밝기가 고르게 늘지 않습니다. 같은 밝기 증가분이라도 파란색은 아주 밝아 보이고 노란색은 거의 안 밝아 보이는 식입니다. hsl의 밝기 축이 사람의 실제 색 지각과 어긋나 있기 때문입니다.

oklch()는 사람의 색 지각에 맞춰 설계된 색 공간입니다. 좌표 세 개로 색을 표현합니다.

oklch(66% 0.18 254); /* L C H */
좌표이름범위의미
LLightness(명도)0%–100%0에 가까울수록 검정, 100에 가까울수록 흰색
CChroma(채도)0 이상(보통 0–0.4)0이면 무채색(회색), 클수록 선명
HHue(색상)0–360색상환 각도. 빨강 근처 25, 노랑 근처 100, 파랑 근처 254

hsl과 다른 점은, oklch에서는 L 값을 고정한 채 H만 바꿔도 눈에 보이는 밝기가 거의 그대로 유지된다는 것입니다. 그래서 “이 파란색과 밝기가 같은 초록색”을 코드로 계산해서 만들 수 있습니다.

hex → oklch 값 계산

design-spec의 --color-primary: #0969da를 oklch로 옮기는 절차는 다음과 같습니다.

  1. 브라우저 개발자 도구의 색상 선택기(ElementsStyles에서 색상 값 옆 네모를 클릭)를 열면 hex·rgb·hsl·oklch 표기를 순환해서 보여줍니다. #0969da를 oklch로 전환하면 oklch(54.6% 0.192 253.7)에 가까운 값이 나옵니다.
  2. 이 좌표를 그대로 쓰지 않고 소수 둘째 자리로 반올림oklch(55% 0.19 254)로 정리합니다. 토큰 값은 사람이 읽고 조정할 것이므로 지나치게 긴 소수를 남기지 않습니다.
  3. 같은 방식으로 나머지 색을 옮깁니다.
토큰기존 hexoklch (반올림)
--color-bg#ffffffoklch(100% 0 0)
--color-surface#f6f7f9oklch(97% 0.003 260)
--color-text#1f2328oklch(24% 0.006 260)
--color-text-muted#59636eoklch(46% 0.012 250)
--color-primary#0969daoklch(55% 0.19 254)
--color-border#d0d7deoklch(87% 0.006 250)
--color-accent#bf3989oklch(50% 0.19 350)

무채색(--color-bg, --color-surface, --color-text)은 C 값이 0에 가깝습니다. H(색상)는 채도가 거의 없을 때는 의미가 작아지지만 표기 규칙상 남겨 둡니다.

color-mix()로 톤 변형 만들기

design-spec에는 --color-primary-hover: #0550ae처럼 hover용 색이 디자이너가 손으로 고른 값으로 이미 정해져 있었습니다. color-mix()를 쓰면 이 값을 별도로 관리하지 않고 기본 색에서 계산해 낼 수 있습니다.

color-mix(in oklch, oklch(55% 0.19 254) 80%, black 20%);

in oklch는 어느 색 공간에서 섞을지 지정합니다. 위 식은 “--color-primary 80퍼센트에 검정 20퍼센트를 섞는다”는 뜻이며, 결과는 원래 색보다 살짝 어두운 톤이 됩니다. black 대신 white를 섞으면 밝은 톤(비활성 상태 등)을 만들 수 있습니다.

흔한 오해

“oklch로 바꾸면 색이 눈에 띄게 달라진다”고 생각하기 쉽지만, 반올림 오차 범위 안에서는 원래 hex와 거의 같은 색으로 보입니다. oklch로 옮기는 목적은 눈에 보이는 색을 바꾸는 것이 아니라, 이후 색 계산(color-mix(), 다크모드 반전)을 예측 가능하게 만드는 것입니다.

실습

1. 파일 열기

portfolio/css/styles.csstokens 레이어를 엽니다.

2. 코드 작성

색 토큰을 oklch로 교체하고, hover·비활성 톤을 color-mix()로 계산합니다.

/* portfolio/css/styles.css */ @layer tokens { :root { /* --color-* : oklch로 교체 */ --color-bg: oklch(100% 0 0); --color-surface: oklch(97% 0.003 260); --color-text: oklch(24% 0.006 260); --color-text-muted: oklch(46% 0.012 250); --color-primary: oklch(55% 0.19 254); --color-border: oklch(87% 0.006 250); --color-accent: oklch(50% 0.19 350); /* hover·비활성 톤은 color-mix()로 계산 */ --color-primary-hover: color-mix(in oklch, var(--color-primary) 80%, black 20%); --color-primary-muted: color-mix(in oklch, var(--color-primary) 20%, var(--color-bg) 80%); --color-accent-hover: color-mix(in oklch, var(--color-accent) 80%, black 20%); --color-border-strong: color-mix(in oklch, var(--color-border) 60%, var(--color-text) 40%); } }

components 레이어에서 계산된 토큰을 사용합니다.

/* portfolio/css/styles.css */ @layer components { .hero .button { background-color: var(--color-primary); } .hero .button:hover { background-color: var(--color-primary-hover); } .project-card-badge-muted { background-color: var(--color-primary-muted); color: var(--color-primary); } .field input:focus, .field textarea:focus { outline: 2px solid var(--color-primary); outline-offset: 2px; border-color: var(--color-border-strong); } }

3. 실행

브라우저에서 portfolio/index.html, contact.html을 새로고침합니다.

4. 확인

  • 히어로의 기본 버튼 색이 이전 hex 값과 거의 같은 파란색으로 보입니다. 눈에 띄는 색 변화가 없어야 정상입니다(반올림 오차 안에서 같은 색).
  • 버튼에 마우스를 올리면 손으로 고른 값 없이도 계산된 어두운 톤으로 바뀝니다.
  • 개발자 도구 콘솔에서 getComputedStyle(document.querySelector('.button')).backgroundColor를 입력하면 oklch(...) 형식의 계산된 색 문자열이 나옵니다.
  • --color-primary의 H 값(254)만 140으로 바꿔 새로고침하면, L·C는 그대로인데 색상만 초록 계열로 바뀌고 버튼과 hover 톤의 명도 관계는 그대로 유지됩니다. 이는 hsl로 같은 실험을 했을 때와 비교해 볼 만합니다.

직접 해보기

  1. --color-accent를 기준으로 color-mix()를 이용해 --color-accent-muted(연한 배경용)를 만들어 보세요. --color-primary-muted와 같은 비율(20%/80%)을 씁니다.
  2. .project-cardborder: 1px solid var(--color-border)가 있다면, hover 시 --color-border-strong으로 바뀌는 규칙을 추가해 보세요.

답 보기

:root { --color-accent-muted: color-mix(in oklch, var(--color-accent) 20%, var(--color-bg) 80%); } .project-card:hover { border-color: var(--color-border-strong); }

자주 하는 실수

증상원인고치는 법
color-mix() 결과가 예상보다 탁하게 나온다in oklch 대신 기본값(in oklab이 아니라 실제로는 지정을 생략하면 srgb)으로 섞였다color-mix()의 첫 인자로 반드시 in oklch(또는 원하는 색 공간)를 명시한다
무채색 토큰(--color-bg 등)의 H 값을 임의로 넣어 색이 미묘하게 틀어진다C(채도)가 0에 가까우면 H는 사실상 의미가 없는데 실수로 채도를 살짝 남겨 색이 낀다완전한 무채색은 oklch(L% 0 0)처럼 C를 0으로 명확히 둔다
oklch 값의 L을 100 넘게 입력해 색이 깨진다L은 백분율 범위(0%–100%)만 유효하다100%를 넘는 값을 넣지 않는다
color-mix()를 지원하지 않는 아주 오래된 브라우저에서 배경이 사라진다폴백 없이 color-mix() 값만 선언했다color-mix()는 2023-05부터 Baseline 널리 사용 가능이라 이 사이트의 대상 브라우저 범위에서는 폴백이 필수는 아니지만, 더 넓은 호환이 필요하면 background: var(--color-primary); background: color-mix(...)처럼 이전 선언을 폴백으로 남긴다

확인 문제

문제 14지선다
oklch가 hsl보다 유리한 이유는?
문제 24지선다
oklch(55% 0.19 254)에서 두 번째 값 0.19가 뜻하는 것은?
문제 34지선다
color-mix(in oklch, var(--color-primary) 80%, black 20%)의 결과는?
문제 44지선다
oklch로 옮긴 색이 원래 hex와 거의 같아 보이는 이유는?

참고 자료

Last updated on