블로그 목록
frontend

CSS color-mix()로 색상 파생시키기 — SCSS darken/lighten 없이

디자인 시스템 작업하다 보면 색 하나에서 파생색을 여럿 만들게 된다. 버튼 기본색이 있으면 hover는 살짝 어둡게, 배경은 아주 연하게, 테두리는 그 중간쯤. 예전엔 이걸 SCSS의 darken(), lighten(), rgba()로 처리했다.

근데 SCSS 색상 함수는 빌드 타임에 값이 고정된다. 브랜드 색을 CSS 변수로 런타임에 바꾸고 싶어도, darken($primary, 10%)$primary가 SCSS 변수일 때만 동작한다. var(--primary)를 넣으면 그냥 안 된다. 다크 모드 만들 때 이 벽에 자주 부딪혔다.

color-mix()는 이걸 CSS 런타임에서 해결한다. 두 색을 지정한 비율로 섞는 함수인데, var()를 그대로 넣을 수 있다.

기본 문법

color-mix(in srgb, red 70%, blue);

in srgb는 어떤 색 공간에서 섞을지다. 첫 번째 색에 70%를 주면 나머지 30%가 두 번째 색이다. 비율은 한쪽만 써도 되고, 생략하면 50:50이다.

파생색 만들기

핵심은 흰색·검은색과 섞어서 밝기를 조절하는 거다. darken은 검정을, lighten은 흰색을 섞으면 된다.

:root {
  --primary: #3b82f6;
}

.btn {
  background: var(--primary);
}

/* 10% 어둡게 — darken 대체 */
.btn:hover {
  background: color-mix(in srgb, var(--primary) 90%, black);
}

/* 아주 연한 배경 — 12%만 섞기 */
.card {
  background: color-mix(in srgb, var(--primary) 12%, white);
}

/* 반투명 테두리 — rgba 대체 */
.chip {
  border: 1px solid color-mix(in srgb, var(--primary) 40%, transparent);
}

transparent와 섞으면 투명도까지 나온다. rgba()로 알파값 따로 계산하던 걸 대체할 수 있다.

다크 모드에서 진가

여기서 SCSS로는 안 되던 게 된다. --primary만 테마별로 바꾸면 파생색이 전부 알아서 따라온다.

:root {
  --primary: #3b82f6;
}
[data-theme='dark'] {
  --primary: #60a5fa;
}

.btn:hover, .card, .chip을 다시 손댈 필요가 없다. 변수 하나 바뀌면 섞인 결과도 런타임에 다시 계산된다. 색 팔레트를 컴포넌트마다 하드코딩하지 않아도 되는 게 제일 크다.

색 공간을 in oklch로 바꾸면 사람 눈에 더 균일한 밝기 변화가 나온다. 미묘한 그라데이션이 필요하면 srgb 대신 oklch를 써보는 걸 추천한다.


color-mix()는 이미 모든 최신 브라우저에서 Baseline으로 쓸 수 있다. SCSS를 색 계산 하나 때문에 붙들고 있었다면, 이제 그 부분은 CSS 변수 + color-mix()로 넘길 수 있다. 브랜드 색 하나에서 UI 전체 색을 파생시키는 구조를 만들면 유지보수가 확실히 편해진다.