색 하나로는 화면을 다 못 칠합니다. 밝은 배경부터 진한 글씨까지 한 색의 여러 단계와 상황별 신호색이 한 벌로 필요합니다. 그 한 벌을 코드로 짜는 방법을 봅니다. 아래 예제는 그대로 확인할 수 있습니다.
51.1 한 색을 명도로 계단 내리기
팔레트의 기본은 한 hue를 밝기 단계로 쪼개는 일입니다. 명도를 위에서 아래로 규칙적으로 내리면 50부터 900까지가 나옵니다.
:root {
--blue-50: hsl(221 90% 97%);
--blue-100: hsl(221 90% 93%);
--blue-300: hsl(221 85% 76%);
--blue-500: hsl(221 83% 53%);
--blue-700: hsl(221 80% 42%);
--blue-900: hsl(221 75% 27%);
}
50은 배경으로 쓸 만큼 옅고 900은 글자로 쓸 만큼 진합니다. 500을 기준으로 위아래로 뻗어 나가는 구조라 기억하기도 쉽습니다.
51.2 단계마다 채도 손보기
명도만 바꾸면 밝은 단계가 붕 뜨거나 진한 단계가 탁해집니다. 밝은 쪽은 채도를 조금 낮추고 진한 쪽은 살짝 올리면 단계가 매끈해집니다.
:root {
--blue-50: hsl(221 60% 97%); /* 채도 낮게 */
--blue-500: hsl(221 83% 53%);
--blue-900: hsl(221 88% 27%); /* 채도 살짝 높게 */
}
옅은 배경이 창백하게 뜨지 않고, 진한 글씨가 흙빛으로 죽지 않습니다. 채도를 단계에 맞춰 미세하게 조율한 결과입니다.
51.3 중립 회색 스케일
텍스트, 테두리, 배경 대부분은 회색이 감당합니다. 브랜드 hue를 아주 조금 머금은 회색으로 한 벌 더 만듭니다.
:root {
--gray-50: hsl(221 14% 98%);
--gray-100: hsl(221 12% 95%);
--gray-300: hsl(221 10% 84%);
--gray-500: hsl(221 8% 55%);
--gray-700: hsl(221 10% 35%);
--gray-900: hsl(221 14% 15%);
}
gray-50은 페이지 배경, gray-300은 테두리, gray-700은 본문 글자에 어울립니다. 채도가 낮아 무채색에 가깝지만 브랜드 파랑 옆에서 겉돌지 않습니다.
51.4 신호색 세 가지
성공, 주의, 위험은 관습적인 색이 있습니다. 이들도 같은 채도와 명도 규칙으로 맞춰 두면 브랜드색과 한 화면에서 조화롭습니다.
:root {
--success: hsl(145 63% 42%);
--warning: hsl(38 92% 50%);
--danger: hsl(0 72% 51%);
--info: hsl(221 83% 53%);
}
.alert-success { background: color-mix(in oklab, var(--success), white 85%); color: var(--success); }
.alert-danger { background: color-mix(in oklab, var(--danger), white 85%); color: var(--danger); }
진한 신호색에서 옅은 배경을 섞어 뽑으니 알림 박스가 자동으로 만들어집니다. 초록, 노랑, 빨강이 명도 대에서 서로 비슷해 나란히 둬도 튀지 않습니다.
51.5 color-mix로 중간값 채우기
단계를 다 손으로 적기 번거로우면 양 끝을 정하고 중간을 섞어 냅니다. 200이 필요하면 100과 300을 반씩 섞습니다.
:root {
--blue-100: hsl(221 90% 93%);
--blue-300: hsl(221 85% 76%);
--blue-200: color-mix(in oklab, var(--blue-100), var(--blue-300));
}
oklab 공간에서 섞으면 사람 눈에 중간처럼 보이는 값이 나옵니다. 단순 rgb 평균보다 단계가 고르게 이어집니다.
51.6 원시 토큰과 시맨틱 토큰
지금까지의 색값은 원시 토큰입니다. 그 위에 역할 별칭을 한 겹 얹으면 화면 코드가 색값 대신 의미를 부릅니다.
:root {
/* 원시 */
--blue-500: hsl(221 83% 53%);
--gray-50: hsl(221 14% 98%);
--gray-300: hsl(221 10% 84%);
--gray-900: hsl(221 14% 15%);
/* 시맨틱 */
--bg: var(--gray-50);
--text: var(--gray-900);
--border: var(--gray-300);
--accent: var(--blue-500);
}
body { background: var(--bg); color: var(--text); }
.card { border: 1px solid var(--border); }
색을 조정할 일이 생기면 원시 토큰만 손보고, 화면 스타일은 그대로 둡니다. 팔레트가 커질수록 이 두 겹 구조가 값을 합니다.
51.7 UI에 실제로 매핑
팔레트는 자리마다 어느 단계를 쓸지 정해 둘 때 완성됩니다. 배경은 옅게, 테두리는 중간, 글자는 진하게가 기본 짝입니다.
.badge {
background: var(--blue-100);
color: var(--blue-700);
border: 1px solid var(--blue-300);
}
.btn-primary {
background: var(--blue-500);
color: #fff;
}
.btn-primary:hover { background: var(--blue-700); }
배지는 옅은 배경에 진한 글자, 버튼은 진한 배경에 흰 글자입니다. 한 색의 계단을 자리별로 나눠 쓰니 색 하나로도 층이 생깁니다.
51.8 다크 테마용 팔레트 겹쳐 두기
팔레트를 짤 때 어두운 테마 값을 같은 이름으로 미리 준비하면 나중에 손댈 일이 줄어듭니다. 시맨틱 토큰만 갈아 끼웁니다.
:root {
--bg: var(--gray-50);
--text: var(--gray-900);
--border: var(--gray-300);
}
@media (prefers-color-scheme: dark) {
:root {
--bg: hsl(221 18% 12%);
--text: hsl(221 14% 90%);
--border: hsl(221 14% 28%);
}
}
원시 스케일은 그대로 두고 역할 토큰의 목적지만 바꿨습니다. 팔레트가 두 테마를 한 벌로 감당합니다.
51.9 팔레트 점검용 미리보기
스케일을 다 만들었으면 한 줄로 늘어놓고 눈으로 확인하는 습관이 좋습니다. 단계가 고르게 이어지는지 바로 보입니다.
.swatches { display: flex; }
.swatches span {
width: 48px;
height: 48px;
}
.s50 { background: var(--blue-50); }
.s100 { background: var(--blue-100); }
.s300 { background: var(--blue-300); }
.s500 { background: var(--blue-500); }
.s700 { background: var(--blue-700); }
.s900 { background: var(--blue-900); }
단계 사이 간격이 들쭉날쭉하면 어느 한 칸에서 명도가 튄 것입니다. 이렇게 늘어놓고 보면 숫자로는 안 보이던 어긋남이 눈에 잡힙니다.
51.10 정리
팔레트는 색을 많이 고르는 일이 아니라 한 색을 규칙적으로 쪼개는 일입니다. 명도로 단계를 내리고 채도를 미세하게 조율하고, 회색과 신호색을 같은 규칙으로 맞추고, 원시 토큰 위에 역할 별칭을 얹으면 화면이 산만해지지 않습니다. 색이 늘어도 규칙이 하나면 팔레트는 흐트러지지 않습니다.