68.1 색을 왜 토큰으로 관리해요?

테마를 배우기 시작하면 제일 먼저 마주치는 게 이에요. 그런데 색을 다루는 방식이 잘못돼 있으면, 다크 모드 하나 붙이려다 온 프로젝트를 헤집게 돼요. 제가 딱 그랬어요. 버튼에 #3b82f6, 링크에 또 #3b82f6, 배지에도 #3b82f6. 같은 파란색을 수십 군데에 손으로 박아놨죠.


그러다 기획에서 "브랜드 색을 좀 더 진한 파랑으로 바꿔주세요" 한마디가 왔어요. 저는 파일 서른 개를 뒤지며 #3b82f6을 찾아 바꿨는데, 몇 군데를 놓쳐서 화면에 옛날 파랑이 섞여 나왔어요. 이게 하드코딩(hardcoding, 값을 그때그때 직접 박아 넣기)의 저주예요. 색은 값이 아니라 이름으로 불러야 해요. 그 이름 붙인 색 하나를 토큰(token, 이름을 붙여 재사용하는 값)이라고 불러요.


상황: 색에 이름을 붙여 한곳에 정의하고, 쓸 때는 이름만 불러요.
:root {
--color-primary: #3b82f6; /* 여기 한 줄만 바꾸면 */
}
.button { background: var(--color-primary); }
.link { color: var(--color-primary); }
.badge { background: var(--color-primary); }


이제 브랜드 색을 바꾸려면 --color-primary 한 줄만 고치면 돼요. 버튼도, 링크도, 배지도 한꺼번에 따라 바뀌죠. 서른 개 파일을 뒤질 일이 사라졌어요. 이 단 하나의 진실(하나만 고치면 전부 반영되는 구조)이 테마 작업의 출발점이에요.

68.2 원시 색이랑 시맨틱 토큰은 뭐가 달라요?

토큰을 한 층으로만 만들면 금방 또 막혀요. 여기서 잘 설계된 시스템은 색을 두 층으로 나눠요. 이게 오늘의 핵심이에요.


첫째 층은 원시 색(primitive, 팔레트에 있는 날것의 색)이에요. --blue-500, --gray-900처럼 그냥 색 그 자체에 번호를 붙인 거예요. 여기엔 의미가 없어요. 그냥 "우리가 쓸 수 있는 물감들"이죠. 둘째 층은 시맨틱 토큰(semantic, 역할에 이름을 붙인 토큰)이에요. --color-primary, --color-text처럼 "이 색이 무슨 역할을 하나"로 이름을 붙이고, 그 값으로 원시 색을 가리켜요.


상황: 아래층은 물감 이름, 위층은 역할 이름. 위가 아래를 가리켜요.
:root {
/* 1층: 원시 색, 그냥 물감 */
--blue-500: #3b82f6;
--gray-50: #f9fafb;
--gray-900: #111827;

/* 2층: 시맨틱 토큰, 역할에 물감을 연결 */
--color-primary: var(--blue-500);
--color-bg: var(--gray-50);
--color-text: var(--gray-900);
}


왜 굳이 두 층이냐고요? 컴포넌트는 오직 시맨틱 토큰만 써요. 버튼은 var(--color-primary)라고만 적지, --blue-500이 뭔지 몰라요. 그래서 나중에 브랜드 파랑을 초록으로 바꾸고 싶으면 --color-primary가 가리키는 물감만 바꿔주면 돼요. 반대로 팔레트의 파랑 톤을 살짝 조정하고 싶으면 --blue-500 한 줄만 손대면 그 물감을 쓰는 모든 역할이 같이 바뀌고요. 물감과 역할을 분리했기에 각각 독립적으로 주무를 수 있는 거예요.

68.3 다크 모드는 토큰을 어떻게 바꿔요?

두 층 구조의 진가는 다크 모드에서 터져요. 여기서 초보들이 흔히 저지르는 실수가 있어요. 다크 모드를 만든다고 원시 색을 바꾸려 드는 거예요. "다크에서는 --gray-900을 밝게 만들자" 같은 거요. 그러면 물감의 의미가 뒤엉켜서 엉망이 돼요.


정답은 시맨틱 토큰의 연결만 바꾸는 거예요. 물감(원시 색)은 그대로 두고, 라이트에서는 --color-text가 어두운 물감을 가리키게, 다크에서는 밝은 물감을 가리키게 가리키는 대상만 갈아끼워요. 역할 이름은 그대로, 그 뒤의 물감만 바뀌는 거죠.


상황: 물감은 고정, 역할이 가리키는 물감만 테마별로 갈아끼워요.
:root { /* 라이트 */
--color-bg: var(--gray-50); /* 밝은 배경 */
--color-text: var(--gray-900); /* 어두운 글자 */
}
[data-theme="dark"] { /* 다크: 연결만 뒤집음 */
--color-bg: var(--gray-900); /* 어두운 배경 */
--color-text: var(--gray-50); /* 밝은 글자 */
}


버튼도 카드도 var(--color-text)만 쓰고 있으니, data-theme만 dark로 바꾸면 화면 전체가 통째로 뒤집혀요. 컴포넌트 코드는 단 한 줄도 안 건드렸는데 말이에요. 이게 두 층으로 나눈 진짜 이유예요. 라이트와 다크는 같은 역할 이름을 공유하고, 그 뒤의 물감만 서로 다른 걸 가리켜요. 테마가 열 개로 늘어도 방식은 똑같아요. 연결표를 하나씩 더 두면 되죠.

68.4 색을 컴포넌트에 직접 쓰면 뭐가 문제예요?

여기까지 오면 절대 규칙 하나가 보여요. 컴포넌트 안에서는 원시 색도, 생 hex도 쓰지 마라. 오직 시맨틱 토큰만요. 이걸 어기면 두 층 구조가 순식간에 무너져요.


제 삽질담이에요. 급하다고 카드 하나에 color: #333이라고 직접 박았어요. 라이트에서는 멀쩡했죠. 그런데 다크 모드로 넘어가니 어두운 배경에 어두운 회색 글자가 겹쳐서 안 보이는 거예요. 배경은 토큰을 따라 까매졌는데, 글자만 #333에 고정돼 안 따라온 거죠. 이런 토큰을 안 탄 색 하나가 다크 모드에 구멍을 뚫어요.


상황: 왼쪽은 테마를 안 타는 지뢰, 오른쪽은 테마를 따라오는 색.
/* 이렇게 하지 마세요 */
.card { color: #333; }

/* 이렇게 하세요 */
.card { color: var(--color-text-muted); }


그래서 저는 규칙을 정했어요. 코드에 hex(#으로 시작하는 색)가 보이면 리뷰에서 막는다. 색이 필요하면 먼저 시맨틱 토큰을 만들고 그걸 쓰라고요. 이 규칙 하나가 "다크에서 안 보여요" 민원의 대부분을 미리 없애줘요.

68.5 토큰 이름은 어떻게 지어요?

마지막 관문은 이름 짓기예요. 토큰 이름을 색깔로 지으면 다시 지옥문이 열려요. --color-blue라고 지어놓고, 나중에 브랜드가 초록으로 바뀌면요? 이름은 blue인데 값은 초록인 거짓말 토큰이 돼버려요.


그래서 이름은 "무슨 색인가"가 아니라 "무슨 역할인가"로 지어요. --color-blue가 아니라 --color-primary, --color-gray가 아니라 --color-text, --color-surface(카드나 패널의 바탕), --color-border(테두리)처럼요. 역할로 지으면 물감이 바뀌어도 이름이 계속 진실이에요.


상황: 왼쪽은 값에 묶인 이름, 오른쪽은 역할에 묶인 이름.
/* 나중에 거짓말이 되는 이름 */
--color-blue: #3b82f6;
--color-light-gray: #f3f4f6;

/* 값이 바뀌어도 진실인 이름 */
--color-primary: #3b82f6; /* 주요 강조 */
--color-surface: #f3f4f6; /* 바탕면 */
--color-danger: #ef4444; /* 위험, 삭제 */


여기에 단계를 붙이면 더 촘촘해져요. --color-text는 본문, --color-text-muted는 흐린 보조 글자, --color-text-inverse는 어두운 배경 위 밝은 글자처럼요. 이렇게 역할 + 변형으로 이름을 나눠두면, 디자이너가 "여기 좀 흐린 글자로" 할 때 이미 있는 토큰으로 바로 대응돼요. 이름을 잘 지어두는 건 미래의 나와 팀에게 주는 지도예요.

68.6 오늘 색상 토큰을 정리해볼게요

테마의 뼈대인 색상 토큰 설계를 훑었어요. 다 한 방향을 가리켜요. 색을 이 아니라 이름으로 다루자는 거요.


색은 손으로 박지 말고 토큰으로 이름 붙여 한곳에서 관리하세요. 토큰은 두 층으로 나눠요. 아래층은 원시 색(--blue-500 같은 물감), 위층은 시맨틱 토큰(--color-primary 같은 역할)이고, 컴포넌트는 오직 위층만 써요. 다크 모드는 물감을 건드리지 말고 역할이 가리키는 물감만 갈아끼우면 화면 전체가 통째로 뒤집혀요. 컴포넌트에 생 hex를 박지 말고, 이름은 색깔이 아니라 역할로 지으세요.


이 설계의 밑바탕엔 한 가지 믿음이 있어요. 같은 것은 한 번만 정의한다. 파란색을 서른 번 적으면 서른 번 틀릴 수 있지만, 한 번만 정의하면 한 번만 고치면 돼요. 색을 이름으로 다루는 습관 하나가, 테마 작업 전체를 헤집기에서 한 줄 고치기로 바꿔놔요.