Learn everyday

[6월 TIL #5] 작업 회고: Design Token-Based UI Architecture

besoluble 장은솔 2026. 6. 9. 22:33

이 글은 Andreas Kutschmann의 Design Token-Based UI Architecture(Martin Fowler, 2024-12-12)를 읽고 정리한 공부 기록이다.
 
앞쪽은 위키처럼 개념을 정리했고, 뒤쪽 TIL에 왜 이걸 공부했고 무엇을 남기고 싶은지를 덧붙였다.


Design Token이란 무엇인가

Design Token은 사용자 인터페이스를 구성하는 시각적 디자인 결정을 데이터로 표현한 것이다. 더 정확히는, Design Tokens Community Group이 토큰을 "디자인 결정을 플랫폼에 종속되지 않는 방식으로 표현해, 여러 직군과 도구와 기술이 함께 공유할 수 있게 하는 방법론"이라고 정의한다. 여기서 중요한 것은 "값"이 아니라 "결정"이다.
 

예를 들어 #0265dc는 그냥 색상값이다. 하지만 --color-accent: #0265dc;처럼 이름을 붙이면, 단순한 색이 아니라 "이 색을 강조색으로 쓴다"는 디자인 결정이 된다. Design Token은 색상, 타이포그래피, 간격, 크기, z-index 같은 시각적 값을 코드 여기저기 흩어두는 대신 이름 붙은 데이터로 관리하는 방식이다. 즉 단순히 CSS 변수를 만드는 일이 아니라, 디자인 의사결정을 제품 코드에서 재사용 가능한 형태로 표현하는 일에 가깝다.

 
원문은 토큰의 핵심 효용으로 "공통 언어"를 든다. 같은 결정을 디자이너, 개발자, PM이 같은 이름으로 부르고, 디자인 도구와 코드와 문서가 같은 출처를 바라보게 만든다는 것이다.

Single Source of Truth

제품이 커질수록 디자인 결정은 여러 곳에 흩어진다. 디자인 도구에는 색상 스타일이 있고, 웹 코드에는 CSS 변수가 있고, 모바일 앱에는 플랫폼별 리소스가 있다. 여러 팀이 동시에 작업하면 각 팀이 자기 화면에서 비슷한 값을 따로 만들기도 한다.
 
처음에는 별문제 없어 보이지만, 시간이 지나면 이런 문제가 쌓인다.

  • 같은 의미의 색을 서로 다른 이름으로 부른다.
  • 같은 버튼인데 화면마다 간격이 다르다.
  • 어떤 화면은 최신 기준을 따르고, 어떤 화면은 예전 값을 그대로 쓴다.
  • 디자인 변경이 생겼을 때 어디를 바꿔야 하는지 추적하기 어렵다.
  • 새로 작업하는 사람이 어떤 기준을 따라야 하는지 알기 어렵다.

Design Token은 이런 문제를 줄이기 위해 디자인과 엔지니어링 사이의 Single Source of Truth 역할을 한다. 하나의 원본 토큰 파일을 기준으로 두고, 그것을 각 플랫폼에 맞는 코드로 변환한다.

Design Token 원본
  ↓
Translation Tool
  ↓
CSS / SCSS / JavaScript / Android XML / iOS resources / Documentation

 
이렇게 하면 여러 플랫폼과 팀이 같은 디자인 결정을 공유할 수 있다.

z-index 사례와 magic numbers

원문은 z-index 사례로 토큰의 필요성을 설명한다. 여러 팀이 각자 다른 UI 영역을 맡는 환경(마이크로 프론트엔드 같은)에서는 navigation, toast, modal, dialog가 한 화면 위에 겹쳐 보인다. 이 쌓임 순서를 맞추려고 z-index를 쓰는데, 각 팀이 임의로 숫자를 정하기 시작하면서 문제가 생긴다.

.navigation {
  z-index: 100;
}

.toast {
  z-index: 999;
}

.modal {
  z-index: 10000;
}

 

이런 값을 magic number라고 한다. 숫자만 봐서는 왜 999인지, 왜 10000인지, 무엇이 무엇보다 위에 떠야 하는지 알 수 없다.

 
이를 토큰으로 바꾸면 이렇게 된다.

--z-index-default: 1;
--z-index-sticky: 100;
--z-index-navigation: 200;
--z-index-spinner: 300;
--z-index-toast: 400;
--z-index-modal: 500;

 

중요한 건 숫자가 사라진 게 아니라, 숫자에 의미 있는 이름이 붙었다는 점이다. 이제 개발자는 임의로 z-index: 9999를 쓰는 대신 상황에 맞는 토큰을 쓴다. z-index: var(--z-index-modal);처럼.

 
이 사례가 보여주는 것은, Design Token이 스타일 값을 모아두는 일이 아니라 여러 사람이 같은 UI 의사결정을 공유하게 만드는 구조라는 점이다.

자동화된 Design Token 배포 파이프라인

원문은 토큰을 여러 팀과 플랫폼에 배포하는 자동화 파이프라인도 설명한다. 대표적인 흐름은 Check → Build → Test → Publish → Notify다.
 
Check. 토큰 파일이 올바른 형식인지 검증한다. JSON 문법이 맞는지, 정해진 규칙을 따르는지 확인한다.
 
Build. 원본 토큰을 각 플랫폼이 이해할 수 있는 코드로 변환한다. JSON 토큰을 웹에서는 CSS 변수로, Android에서는 XML로, Sass에서는 SCSS 변수로 바꾼다. 이때 Style Dictionary 같은 도구를 쓴다.

tokens.json
  ↓
variables.css / tokens.scss / android.xml / ios resources

 
Test. 변환된 토큰이 실제로 문제없이 동작하는지 확인한다. 색상 대비가 접근성 기준을 만족하는지, Storybook에서 컴포넌트가 깨지지 않는지, 시각적 회귀 테스트에서 의도치 않은 변화가 없는지 검증한다.
 
Publish. 산출물을 npm 같은 패키지 저장소에 배포한다. 제품 팀은 dependency를 업데이트하는 방식으로 새 토큰 버전을 가져온다.
 
Notify. 새 버전이 배포되었음을 팀들에게 알린다. 이메일이나 채팅으로 "새 토큰 버전이 나왔으니 업데이트하세요"라고 안내한다.

Design Token을 레이어로 나누기

원문에서 가장 중요하게 느껴진 부분은 토큰을 레이어로 나누는 관점이었다. 모든 디자인 결정이 같은 추상화 수준에 있지는 않다. 예를 들어 다음 네 가지는 모두 색상이지만 서로 다른 층위에 있다.

#0265dc
blue-900
color.accent
button.primary.background

 

#0265dc는 실제 색상값, blue-900은 쓸 수 있는 색 선택지, color.accent는 이 색을 강조색으로 쓰겠다는 의미 기반 결정, button.primary.background는 그 강조색을 primary button 배경에 적용하겠다는 구체적 결정이다.

 
원문은 이를 세 레이어로 나누고, 각 레이어를 What / How / Where 질문으로 설명한다.

Option Token     (What)
  ↓
Decision Token   (How)
  ↓
Component Token  (Where)

 
Option은 "무엇을 쓸 수 있는가", Decision은 "그것을 언제, 어떻게 쓰는가", Component는 "어디에 쓰는가"에 답한다. 추상적 가능성에서 의미로, 다시 구체적 적용으로 내려가는 흐름이다.

Option Token

Option Token은 쓸 수 있는 디자인 선택지를 정의한다. primitive token, base token, core token, foundation token, reference token이라고도 부른다. 색상 팔레트, 간격, 타이포그래피라면 다음과 같은 값들이 여기에 해당한다.

// 색상
blue-100  blue-200  blue-300  blue-900
gray-100  gray-900  white

// 간격
spacing-1  spacing-2  spacing-4  spacing-8

// 타이포그래피
font-size-12  font-size-14  font-size-16
font-weight-regular  font-weight-semibold

 

Option Token은 "우리 제품에서 쓸 수 있는 값의 후보군"을 준다. 하지만 그것만으로는 부족하다. blue-900이라는 이름만 봐서는 이 색을 버튼 배경에 쓰는지, 링크 색인지, 강조 텍스트인지, hover 상태인지 알 수 없다. 즉 option token은 선택지를 제한해주지만 사용 맥락은 설명해주지 않는다.

Decision Token

Decision Token은 option token에 의미를 부여한다. semantic token, system token이라고도 부른다. 예를 들어 색상 option token이 다음처럼 의미를 갖게 된다.

gray-100  →  surface
gray-200  →  background-disabled
gray-400  →  text-disabled
gray-900  →  text
blue-900  →  accent
white     →  text-on-accent

 
코드로 보면 값 중심과 의미 중심의 차이다.

/* 값 중심 */
--color-blue-900: #0265dc;

/* 의미 중심 */
--color-accent: var(--color-blue-900);

 

토큰 원본 파일에서는 이 참조를 중괄호 문법으로 표현한다. 예를 들어 color.accent의 값으로 "{color.options.blue-900}"이라고 적는 식이다. 이렇게 별칭(alias)으로 엮어두면, 팔레트 값을 바꿔도 그 값을 참조하는 컴포넌트 코드는 건드리지 않아도 된다. CSS의 var()는 이 참조가 웹으로 컴파일된 형태라고 볼 수 있다.

 

개발자는 이제 blue-900을 직접 고르는 대신 상황에 맞게 color.accent를 쓴다. Decision Token은 "어떤 값을 골라야 하지?"라는 부담을 줄이고, "이 UI에 필요한 의미가 무엇이지?"를 생각하게 만든다.

Component Token

Component Token은 decision token을 특정 컴포넌트의 특정 부위에 매핑한다. 버튼 컴포넌트라면 다음과 같은 토큰을 만들 수 있고, 이 토큰들은 decision token을 참조한다.

--button-primary-background: var(--color-accent);
--button-primary-text: var(--color-text-on-accent);

--button-secondary-background: var(--color-surface);
--button-secondary-text: var(--color-text);

--button-disabled-background: var(--color-background-disabled);
--button-disabled-text: var(--color-text-disabled);

 
Component Token은 "이 디자인 결정이 컴포넌트의 어디에 적용되는가"를 명확하게 한다. 이 레이어가 있으면 컴포넌트 구현자가 매번 색 조합이나 간격을 다시 판단하지 않아도 된다.
 
다만 component token이 많아질수록 토큰 수와 유지보수 비용이 커진다. 원문은 모든 component token이 대응하는 decision token을 가질 필요는 없다고 본다. 일반화된 결정이 아직 없거나 굳이 만들 필요가 없는 "스노우플레이크(snowflake)" 케이스에서는 레이어를 건너뛰어도 된다. 그래서 모든 프로젝트가 처음부터 3-layer 구조를 가질 이유는 없다.

1-layer, 2-layer, 3-layer

레이어를 몇 개 쓸지도 프로젝트 상황에 따라 정한다. 프로젝트 규모, 디자인 변경 빈도, 팀 구조, 필요한 유연성에 따라 레이어 수를 정해야 한다.
 

1-layer. 가장 단순하다. blue-900, gray-100, spacing-4처럼 값만 둔다. 단순한 게 장점이고, 의미가 부족한 게 단점이다.

 
2-layer. Option Token과 Decision Token을 나눈다. 값의 선택지를 관리하면서 제품 코드에서는 의미 기반 토큰을 쓸 수 있어, 비교적 현실적인 출발점이 된다.
 
3-layer. Option, Decision, Component를 모두 쓴다. 특정 컴포넌트의 어느 부위에 어떤 토큰을 써야 하는지 명확해 개발자 경험이 좋지만, 토큰 수와 관리 비용이 늘어난다.

Token Scope

또 하나 중요했던 개념은 token scope다. 모든 토큰을 모든 개발자에게 공개해야 하는 건 아니다.
 

option token은 디자이너에게는 유용하다. 전체 색상 팔레트를 보고 고를 수 있기 때문이다. 하지만 애플리케이션 개발자에게 모든 option token을 열어두면 오히려 혼란이 생긴다. 개발자는 blue-900, gray-400, gray-700 중에서 직접 고르기보다 color.text, color.accent, color.disabled 같은 의미 기반 토큰을 쓰는 편이 안전하다.

 
그래서 토큰을 public과 private/internal로 나누는 전략이 필요하다.

Private / Internal Token
  blue-100  blue-200  blue-900
  gray-100  gray-900

Public Token
  color.text  color.surface  color.accent
  button.primary.background

 
이렇게 하면 제품 코드에서 의미 기반 토큰을 쓰도록 유도할 수 있고, 내부 option token을 나중에 바꾸거나 없애도 외부에 breaking change를 덜 만든다. 다만 token scope는 아직 W3C 표준 스펙에 포함되어 있지 않아서, Salesforce Lightning Design System처럼 자체 플래그와 도구로 구현해야 한다. 표준이 아니라는 점은 기억해둘 필요가 있다. 결국 Token scope는 단순히 파일을 나누는 문제가 아니라, "어떤 디자인 결정에 누가 접근할 수 있는가"에 대한 설계다.

Semantic Naming

원문에 "semantic naming"이라는 별도의 장이 있는 건 아니지만, decision token과 component token 설명은 사실상 이름 짓기에 대한 이야기로 읽혔다. 핵심 질문은 세 가지다. 값을 이름으로 부를 것인가, 역할을 이름으로 부를 것인가, 적용 위치를 이름으로 부를 것인가.

/* 값 중심 — "무엇인가" */
--color-blue-900  --color-gray-100  --font-size-14

/* 역할 중심 — "왜 쓰는가" */
--color-accent  --color-surface  --color-text  --color-text-disabled

/* 위치 중심 — "어디에 쓰는가" */
--button-primary-background  --button-primary-text  --button-icon-size

 
값 중심 이름은 "무엇인가"를, 역할 중심 이름은 "왜 쓰는가"를, 컴포넌트 이름은 "어디에 쓰는가"를 말한다. 토큰을 제대로 관리하려면 이 이름들이 각각 어느 레이어에 속하는지 구분할 수 있어야 한다. 원문도 인정하듯 이름 짓기는 "the hard part"다. 업계에서도 아직 합의되지 않은, 토큰에서 가장 어려운 부분이다.

Design Token이 필요하지 않을 수도 있는 경우

원문에서 좋았던 점은 Design Token을 무조건 도입하라고 말하지 않는다는 것이다. 장점은 분명하다.

  • 디자인 변경을 더 빠르게 전파할 수 있다.
  • 여러 플랫폼과 애플리케이션에서 일관성을 유지할 수 있다.
  • 디자인과 개발 사이의 공통 언어가 된다.
  • 큰 팀에서 협업 기준을 만들 수 있다.
  • 자동화 파이프라인과 결합하면 업데이트 비용을 줄일 수 있다.

하지만 단점도 있다.

  • 초기 구축 비용이 든다.
  • 디자이너가 어느 정도 Git이나 개발 워크플로를 이해해야 할 수 있다.
  • 표준화가 아직 진행 중인 영역이다.
  • 작은 프로젝트에서는 관리 비용이 이득보다 클 수 있다.

그래서 Design Token은 여러 플랫폼을 지원하거나, 여러 애플리케이션과 프론트엔드가 있거나, 디자인 변경이 잦거나, 큰 팀이 함께 작업하거나, CI/CD 자동화에 익숙한 환경에서 특히 유용하다. 반대로 규모가 작고 디자인 복잡도가 낮고 협업 문제가 크지 않다면, 굳이 큰 토큰 시스템을 만들 필요는 없다. 디자인 시스템은 실제로 풀어야 할 문제가 있을 때 도입하는 것이다.


TIL — 왜 공부했고, 무엇을 남길까

이 글을 공부하게 된 배경

최근에 타이포 시스템, 아이콘이 들어간 버튼, 아이콘만 있는 버튼을 주요 화면부터 맞추는 작업을 했다. 처음에는 디자인 시스템을 적용하면 기준이 한 번에 전체 UI로 잘 퍼질 거라고 생각했다. 하지만 실제 제품 코드는 그렇게 단순하지 않았다. 어떤 곳에서는 새 스타일이 잘 적용됐지만, 어떤 곳에서는 상위 컴포넌트의 조건이나 기존 스타일, local override, utility class, component prop 같은 것이 더 강하게 작동해서 의도한 스타일이 적용되지 않았다. 처음에는 "왜 CSS가 안 먹지?" 하는 문제처럼 느꼈다.
 
그런데 이 글을 읽고 나니, 내가 겪은 건 단순한 CSS 문제가 아니라 스타일 결정권이 여러 레이어에 흩어져 있는 문제에 가까웠다.

공통 기준 ➔ 컴포넌트 스타일 ➔ 상위 wrapper ➔ feature-specific style ➔ utility class ➔  local override 
➔  실제 화면

 
디자인 시스템 적용은 "기준을 만들면 끝"이 아니었다. 기준이 어떤 경로로 실제 화면까지 내려가는지, 그리고 어디서 덮어써질 수 있는지까지 봐야 했다.

이번에 가장 크게 배운 것

이번에 가장 크게 배운 건, Design Token의 목적이 "값을 통일하는 것"이 아니라 "의도를 관리하는 것"이라는 점이다. 예전에는 색이나 타이포 값을 변수로 빼고 공통 컴포넌트를 만들면 디자인 시스템에 가까워진다고 생각했다. 물론 그것도 중요하다. 하지만 이 글이 말하는 Design Token은 조금 더 넓은 관점을 가진다. 중요한 건 다음 질문들이다.

이 값은 단순한 선택지인가?
이 값은 의미를 가진 결정인가?
이 값은 특정 컴포넌트의 어느 부분에 적용되는가?
이 값은 전역에서 써도 되는가?
이 값은 특정 화면 안에서만 유효한가?
이 값을 다른 사람이 직접 써도 되는가?

 즉 디자인 시스템은 값의 묶음이 아니라 의사결정의 구조다.

나만의 정리로 끝내지 않기

이번 작업을 하면서 아쉬웠던 부분도 있다. 작업 자체는 했지만, 그 기준을 어떻게 모두의 공동 약속으로 끌어올릴지에 대한 고민은 부족했다. 머릿속에는 어느 정도 기준이 생겼지만, 코드나 문서에 남지 않으면 결국 나만의 정리로 끝날 수 있다. 디자인 시스템은 혼자 아는 규칙으로는 작동하지 않는다. 다른 개발자도, 디자이너도, AI 에이전트도 참고할 수 있는 형태로 남아 있어야, 다음 작업에서 같은 결정을 반복하지 않고 같은 기준을 이어갈 수 있다. 그래서 이번에 정한 것들은 완벽하지 않더라도 Git에 기록해보려고 한다. 처음부터 거대한 디자인 시스템 문서를 만들 필요는 없을 것 같다. 최소한 이 정도는 남길 수 있다.

# Token Usage Notes

## Global tokens
제품 전반에서 공유되는 의미 기반 토큰으로 사용한다.

## Page-level tokens
특정 화면 안에서만 유효한 토큰으로 사용한다.
다른 화면에서도 같은 의미가 반복되면 공통 토큰으로 승격을 검토한다.

## Component rules
반복되는 컴포넌트의 크기, 간격, 타이포그래피, 상태별 스타일은
컴포넌트 규칙으로 관리한다.

## Override policy
반복되는 값은 의미 기반 토큰이나 컴포넌트 규칙으로 올린다.
local override는 예외 상황에서만 허용한다.

AI와 함께 일하기 위한 기준

요즘은 AI 에이전트와 함께 작업하는 일이 많아지면서, 사람이 어떤 기준을 내려줘야 하는지 더 자주 생각하게 된다. 기준이 명확하면 AI는 그 기준에 맞춰 반복 작업을 잘 해낸다. 반대로 기준이 모호하면 AI는 그럴듯한 코드를 만들 수는 있어도, 제품 전체의 일관성을 지키기는 어렵다. 그래서 Design Token 공부는 단순한 프론트엔드 스타일링 공부가 아니라, AI와 함께 일하기 위한 기준 설계 공부처럼 느껴졌다. 좋은 기준을 세워야 좋은 결과를 얻고, 그 기준은 머릿속이 아니라 코드와 문서에 남아 있어야 한다.

내일 해볼 것

내일은 이번에 작업한 스타일들을 다시 보면서 다음 질문을 해보려고 한다. 완벽한 디자인 시스템을 만들겠다며 작업의 규모를 크게 벌리는 걸 지양하면서도, 최소한 내가 내린 결정이 일관성을 가지고 제품의 디자인 품질을 높이는 데에 기여하는 시작이 되는 것이 목표다.

이 값은 option token인가, decision token인가, component token인가?
이 값은 전역에서 쓰일 값인가, 특정 화면 안에서만 유효한 값인가?
제품 코드에서 직접 접근해도 되는 public token인가?
반복되는 local override가 있는가?
반복된다면 semantic token이나 component rule로 올릴 수 있는가?
이번 작업에서 정한 기준을 어디에 기록할 것인가?

정리

  1. 디자인 토큰은 값이 아니라 의도를 관리한다.
  2. 토큰은 option, decision, component처럼 레이어로 나눠 생각해야 한다.
  3. 시스템 적용은 선언으로 끝나지 않고, 어떤 값이 어디까지 적용되고 어디서 override될 수 있는지까지 정해야 한다.

Design Token은 UI 값을 변수로 빼는 기술이 아니다. 사용자 인터페이스를 구성하는 시각적 디자인 결정을 데이터로 표현하고, 그 결정을 제품 전체에 일관되게 전파하기 위한 아키텍처다. 작업 전에 이 글을 먼저 읽었다면 더 좋았을 수도 있다. 하지만 지금이라도 공부해서 내가 한 작업을 더 정확히 해석할 수 있게 된 것도 충분히 좋다. 굿!