-
디자인 토큰은 색상 상수 모음이 아니다 — 이름을 두 겹으로 두는 이유IT 2026. 8. 10. 22:00
디자인 시스템 이야기를 읽다 보면 "디자인 토큰"이라는 말이 반드시 나온다. 나는 오랫동안 이걸 색상 상수 모음 정도로 알고 있었다. 팔레트를 파일 하나에 모아 이름 붙여 두는 것, 그러니까 CSS 변수를 쓸 줄 알면 이미 토큰을 쓰고 있는 거라고 생각했다.
제대로 찾아보니 생각보다 오래됐고 생각보다 정교했다. 용어 자체는 2014년 Salesforce에서 Jina Anne이 만들었고, 지금은 W3C 커뮤니티 그룹의 표준 명세까지 나와 있다. 그리고 내가 하던 그 이해가 정확히 가장 흔한 오해였다 — 디자인 토큰은 색상 상수 모음이 아니다. CSS 변수와 토큰을 같은 것으로 보는 순간 이 개념의 핵심을 통째로 놓친다.
2014년, "이 결정을 어디에나 배포하고 싶다"
출발점은 Salesforce의 디자인 시스템(제품 전반의 UI 규칙과 부품을 한데 모아 관리하는 체계) 작업이었다. 문제는 단순했다. 브랜드 색을 하나 정했는데, 그걸 써야 할 곳이 웹만이 아니었다. iOS 앱, 안드로이드 앱, 그리고 파트너사들이 각자 만드는 구현까지 같은 색을 써야 했다.
여기서 나온 발상이 "디자인 결정을 값이 아니라 데이터로 한 번만 적어두고, 각 플랫폼이 알아서 자기 형식으로 변환해 가져가게 하자"는 것이었다. 그래서 토큰의 원본은 특정 언어에 속하지 않는 중립 데이터(JSON이나 YAML)로 적는다. Salesforce는 이걸 변환해 주는 Theo라는 도구를 만들어 썼고, 이후 Amazon이 만든 Style Dictionary(토큰 파일 하나를 받아 웹·iOS·안드로이드용 코드로 각각 뽑아내는 빌드 도구)가 사실상 표준 도구가 됐다.
여기까지만 보면 "설정 파일을 플랫폼별로 변환하는 것" 정도로 들린다. 실제 핵심은 그다음에 있다.
변수와 무엇이 다른가 — 이름이 한 겹 더 있다
토큰을 처음 배울 때 대부분 이렇게 시작한다.
/* 1단계 — 값에 이름을 붙였다. 여기까지는 그냥 '변수'다. */ :root { --blue-500: #0052CC; --red-500: #DE350B; } .submit-button { background: var(--blue-500); } .error-message { color: var(--red-500); }코드 설명. 이 코드는 하드코딩을 없앴다는 점에서 분명히 진전이다. 색을 바꾸고 싶으면 맨 위 한 곳만 고치면 된다. 하지만 여기엔 결정적인 정보가 빠져 있다 — 이 파란색이 왜 이 버튼에 쓰였는지가 코드에 없다. 나중에 누군가 "브랜드 색을 초록으로 바꾸자"고 하면
--blue-500이라는 이름부터 거짓말이 된다. 파란색이 아닌데 이름이 파란색이니까. 그렇다고 이름을 안 바꾸면 코드 전체가 혼란스러워지고, 바꾸면 이 변수를 쓰는 모든 곳을 다시 확인해야 한다. 값에만 이름을 붙였을 뿐, 용도에는 이름을 붙이지 않았기 때문에 생기는 문제다.디자인 토큰은 여기서 한 겹을 더 넣는다.
/* 1단계 — 원시 토큰: 팔레트에 이름을 붙인다. 용도는 모른다. */ :root { --blue-500: #0052CC; --red-500: #DE350B; /* 2단계 — 의미 토큰: '어디에 쓰는 색인지'에 이름을 붙인다. 값을 직접 적지 않고 위의 원시 토큰을 가리킨다(참조). */ --color-action-primary: var(--blue-500); --color-text-danger: var(--red-500); } /* 화면을 그리는 코드는 2단계만 쓴다. 팔레트를 직접 만지지 않는다. */ .submit-button { background: var(--color-action-primary); } .error-message { color: var(--color-text-danger); }코드 설명. 달라진 건 중간에 낀 두 줄뿐인데 성질이 완전히 바뀐다. 이제 화면을 그리는 코드는 "파란색을 칠해라"가 아니라 "주요 동작에 해당하는 색을 칠해라"고 말한다. 브랜드 색을 초록으로 바꾸는 작업은 이제
--color-action-primary가 가리키는 대상을 한 줄 바꾸는 일이 되고, 화면 코드는 한 줄도 건드리지 않는다. 여기서 첫 단계를 원시 토큰(primitive, 맥락 없는 날값), 두 번째 단계를 의미 토큰(semantic, 용도에 이름을 붙인 것)이라 부른다. 의미 토큰은 별칭 토큰(alias)이나 결정 토큰(decision token)이라고도 불리는데, 결정이라는 표현이 성격을 가장 잘 드러낸다. 디자인 결정이 실제로 기록되는 자리가 바로 이 겹이기 때문이다. 놓치기 쉬운 함정은 첫 단계만 만들어 놓고 "토큰 도입 완료"라고 여기는 것이다. 그건 변수에 규칙적인 이름을 붙인 것이지, 아직 디자인 결정을 기록한 게 아니다.세 겹까지 가는 경우
다이어그램 설명. 토큰이 화면까지 닿는 참조 사슬을 그린 것이다. "blue-500 은 #0052CC"라는 팔레트 정의에서 출발해, "주요 동작 색 은 blue-500"이라는 용도 지정을 거치고, 필요하면 "버튼 배경 은 주요 동작 색"처럼 특정 부품 전용 이름을 한 겹 더 얹은 뒤 실제 버튼으로 그려진다. 마지막 겹을 컴포넌트 토큰이라 부르는데, 버튼만 예외적으로 다른 색을 써야 하는 상황을 다른 화면에 영향 없이 처리하려고 둔다. 다만 이건 필수가 아니라서 두 겹만 쓰는 팀도 많다.
이 그림에서 진짜 봐야 할 것은 점선이다. 테마를 바꿀 때 갈아 끼우는 자리가 "주요 동작 색 은 blue-500"이라는 용도 지정 한 겹뿐이라는 사실이 이 구조의 존재 이유다. 팔레트 정의는 그대로 두고, 화면을 그리는 코드도 그대로 두고, 가운데 한 겹만 다른 값을 가리키게 만들면 전체 테마가 바뀐다. 겹을 하나 더 넣어서 얻는 게 뭐냐는 질문의 답이 여기 있다 — 바꿔야 할 곳을 한 군데로 몰아넣는 것이다.
겹이 하나면 다크 모드에서 무너진다
말로만 하면 와닿지 않으니 다크 모드로 확인해 보자. 이제 표준 형식으로 적어 본다. W3C 커뮤니티 그룹이 만든 디자인 토큰 명세는 2025년 10월 28일 첫 안정 버전에 도달했고, 토큰 파일은 대략 이런 모양이다.
{ "color": { "blue-500": { "$type": "color", "$value": "#0052CC" }, "blue-200": { "$type": "color", "$value": "#4C9AFF" }, "action-primary": { "$type": "color", "$value": "{color.blue-500}" } } }▲ 기본 테마 — 팔레트와 용도 지정이 함께 있다
{ "color": { "action-primary": { "$type": "color", "$value": "{color.blue-200}" } } }▲ 다크 테마 — 용도 지정 한 줄만 덮어쓴다
코드 설명. 위쪽이 기본 테마, 아래쪽이 다크 테마에서 덮어쓰는 파일이다. 다크 테마 파일에 팔레트가 아예 없다는 점이 핵심이다. 어두운 배경에서는 진한 파랑이 잘 안 보이니 밝은 파랑으로 바꿔야 하는데, 바뀐 건 "주요 동작 색이 어느 팔레트를 가리키는가" 한 줄뿐이다. 중괄호로 감싼
{color.blue-500}이 다른 토큰을 가리키는 참조 문법이고, 이 참조를 바꾸는 것만으로 테마가 갈린다. 만약 원시 토큰 한 겹만 있었다면 어땠을까. 화면 코드가blue-500을 직접 쓰고 있을 테니, 다크 모드를 만들려면 화면 코드를 전부 찾아다니며 조건 분기를 넣어야 한다. 겹 하나 차이가 "설정 파일 한 줄"과 "전면 수정"을 가른다.원래 목적은 플랫폼을 넘는 것이었다
표준 형식이 왜 중요한지도 여기서 이어진다. 토큰 원본이 특정 언어에 매이지 않은 데이터로 적혀 있으니, 빌드 도구가 이걸 읽어 각 플랫폼 문법으로 뽑아낼 수 있다. 웹에는 CSS 변수로, 안드로이드에는 Compose 테마 상수로, iOS에는 SwiftUI 상수로 나간다. 2014년 Salesforce가 처음 풀려던 문제가 정확히 이거였고, 표준 명세가 안정판에 도달했다는 건 이 변환의 입력 형식을 이제 업계가 같이 쓴다는 뜻이다.
여기서 한 가지 덧붙일 게 있다. 플랫폼마다 나가는 결과물은 달라도 의미 토큰의 이름은 그대로 유지된다. 웹 개발자와 안드로이드 개발자가 서로 다른 코드를 짜면서도 "주요 동작 색"이라는 같은 단어로 대화할 수 있게 되는 것인데, 실무에서는 이게 색을 맞추는 것보다 더 큰 이득인 경우가 많다.
정리 — 토큰은 이름을 보장하지, 옳은 이름을 보장하지 않는다
정리하면 디자인 토큰의 정체는 이렇다. 색상 상수 모음이 아니라 디자인 결정에 이름을 붙여 한 곳에 모아둔 것이고, 그 핵심 장치는 값과 용도 사이에 이름을 한 겹 더 끼워 넣는 것이다. 그 한 겹이 테마 전환과 브랜드 변경과 다중 플랫폼 배포를 전부 "한 군데만 고치는 일"로 바꿔 놓는다. 겹을 세 개까지 늘리는 경우도 있지만 원리는 같다.
다만 토큰 체계가 해주지 않는 일도 분명히 알아둘 필요가 있다. 토큰은 "승인된 이름 중 하나를 골랐다"는 것까지만 보장한다. 개발자가 브랜드 색이 들어갈 자리에 위험 표시 색 토큰을 갖다 써도 문법은 완벽하고 린트도 통과한다. 둘 다 승인된 이름이기 때문이다. 토큰은 선택지를 좁히는 장치이지 옳은 선택을 강제하는 장치가 아니다.
그래서 도입할 때 기대치를 이렇게 잡는 게 맞다고 본다. 토큰은 디자인을 지켜 주는 도구가 아니라 디자인을 바꾸기 쉽게 만드는 도구다. 목적을 "일관성 확보"로 잡으면 실망하기 쉽고, "변경 비용 절감"으로 잡으면 정확히 그만큼 돌려받는다. 개인 프로젝트라도 다크 모드를 지원할 생각이 있다면 그 순간부터 겹 하나는 값을 한다.
참고한 공개 자료:
- Design Tokens Community Group — 명세 첫 안정 버전(2025.10) 발표, 2025-10-28 — https://www.w3.org/community/design-tokens/2025/10/28/design-tokens-specification-reaches-first-stable-version/
- Design Tokens Format Module 2025.10 — 토큰 파일 형식과 참조 문법 — https://www.designtokens.org/tr/2025.10/format/
- Smashing Podcast — Jina Anne, "What Are Design Tokens?" (용어 창안자 인터뷰) — https://www.smashingmagazine.com/2019/11/smashing-podcast-episode-3/
- The incomplete history of Design Tokens — 2014년 Salesforce 기원 정리 — https://www.designsystemscollective.com/the-incomplete-history-of-design-tokens-61581c573e5d
- Theo — Salesforce가 만든 초기 토큰 변환 도구 — https://github.com/salesforce-ux/theo
- Style Dictionary — 토큰을 플랫폼별 코드로 뽑아내는 빌드 도구 — https://github.com/amzn/style-dictionary
- Naming Tokens in Design Systems (Nathan Curtis) — 이름 짓기 분류 체계 — https://medium.com/eightshapes-llc/naming-tokens-in-design-systems-9e86c7444676
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
디자인 토큰이 드디어 표준이 됐다 — W3C DTCG 2025.10 안정판이 정한 세 가지 (0) 2026.08.10 린트라는 이름은 건조기 필터에서 왔다 — 1978년 C 코드 검사기가 디자인 토큰까지 온 길 (0) 2026.08.10 6.86배 빨라졌다는데 왜 못 믿나 — 성능 최적화 자동화의 best-of-N 함정 (0) 2026.08.09 AI가 시안대로 UI를 짜도, 맞는지 자동으로 검증할 수가 없다 — 개발 자동화 루프의 진짜 병목 (0) 2026.08.09 남의 AI를 내 안에서 실행한다 — Claude Code를 붙이는 subprocess 하나와 두 개의 브리지 (0) 2026.08.08 격리된 내 AI에 Claude Code를 붙이는 네 갈래 — 그리고 'ACP'라는 이름이 둘이었다 (0) 2026.08.08 MCP를 전부 컨테이너에 가뒀다, 딱 하나만 빼고 — 샌드박스 담에 문을 내는 법과 그 대가 (0) 2026.08.07 샌드박스 정책에 반드시 들어가는 세 줄 — 그리고 각 줄이 막는 공격 (0) 2026.08.07 130개의 보안 경고를 가진 에이전트를 집에서 안전하게 돌리는 법 — OpenShell로 케이지에 넣기 (0) 2026.08.07 에이전트를 샌드박싱한다 — 그 한 문장이 숨기는 세 개의 경계 (0) 2026.08.07