Style Dictionary v4 + W3C DTCG로 웹·iOS·Android 디자인 토큰 파이프라인 한 번에 구축하기
디자인 시스템을 운영해본 팀이라면 한 번쯤 이런 상황을 겪어봤을 것입니다. 디자이너가 브랜드 컬러를 한 줄 바꿨는데, 웹 팀·iOS 팀·Android 팀이 각자 따로 파일을 수정하고, 누군가는 놓치고, 결국 플랫폼마다 미묘하게 다른 색이 배포됩니다. 디자인 시스템이 없는 팀이라면 이 문제가 매 스프린트마다 반복되고, 이미 운영 중인 팀이라면 멀티 브랜드·다크 모드 확장 시점에 같은 벽에 부딪힙니다. 이 비효율을 근본적으로 해결하는 것이 바로 디자인 토큰(Design Tokens) 과 그것을 자동화하는 빌드 파이프라인입니다.
오픈소스 커뮤니티가 주도하는 Style Dictionary v4와 W3C DTCG(Design Tokens Community Group) 표준 포맷을 활용하면, 단일 JSON 소스에서 CSS 변수·Swift 상수·Android XML 리소스를 동시에 자동 생성할 수 있습니다. 이 접근법은 Figma, Penpot, Sketch를 포함한 10개 이상의 도구가 이미 지원하고 있으며, IBM Carbon·Atlassian·SAP Fiori 같은 엔터프라이즈 디자인 시스템도 채택한 방향입니다.
이 글을 읽고 나면 DTCG 포맷으로 토큰을 정의하고, Style Dictionary v4 설정 파일 하나로 세 플랫폼 출력물을 자동 생성하는 파이프라인을 직접 구성할 수 있게 됩니다.
핵심 개념
디자인 토큰이란 무엇인가
디자인 토큰은 색상, 타이포그래피, 간격, 그림자 같은 디자인 의사결정을 플랫폼에 독립적인 키-값 쌍으로 표현한 것입니다. 예를 들어 color-primary: #0055FF라고 정의된 토큰 하나가 웹에서는 CSS 커스텀 프로퍼티(--color-primary)로, iOS에서는 Swift 상수로, Android에서는 XML 색상 리소스로 변환됩니다.
단일 진실 공급원(Single Source of Truth): 토큰을 한 곳에서 정의하고 모든 플랫폼이 그것을 소비하는 구조. 값이 바뀌면 전체가 동시에 갱신됩니다.
토큰은 보통 세 계층으로 나뉩니다.
| 계층 | 이름 | 예시 | 역할 |
|---|---|---|---|
| 1 | Primitive | blue-500: #0055FF |
원시값, 팔레트 정의 |
| 2 | Semantic | color-primary: {blue-500} |
의미 부여, 테마 전환 포인트 |
| 3 | Component | button-bg: {color-primary} |
컴포넌트 전용 매핑 |
이 계층 구조를 따르면 다크 모드 전환 시 Semantic 계층의 참조만 바꾸면 되므로, 컴포넌트 코드를 전혀 건드리지 않고 테마를 교체할 수 있습니다.
W3C DTCG 포맷: 왜 표준이 중요한가
디자인 토큰을 정의하는 방식이 팀마다 제각각이라면, 도구가 바뀔 때마다 마이그레이션 비용이 발생합니다. 이 문제를 해결하기 위해 W3C Design Tokens Community Group이 JSON 기반의 교환 표준을 만들었고, 2025년 10월 28일에 Design Tokens Format Module 2025.10을 첫 번째 안정 버전으로 공식 발표했습니다.
DTCG 포맷의 핵심은 $ 접두사 키입니다.
{
"color": {
"brand": {
"primary": {
"$value": "#0055FF",
"$type": "color",
"$description": "브랜드 주 색상"
}
}
},
"size": {
"spacing": {
"sm": { "$value": "8px", "$type": "dimension" },
"md": { "$value": "16px", "$type": "dimension" }
}
}
}
$type의 역할: Style Dictionary v4는 이 필드를 보고 플랫폼별 단위 변환을 자동으로 처리합니다.dimension타입이면 iOS 출력 시px를pt로 변환하는 방식입니다.
파일 확장자는 .tokens.json을 사용하고, 참조(Reference) 토큰은 "$value": "{color.brand.primary}" 형태로 다른 토큰을 가리킬 수 있습니다. 이 참조 메커니즘이 테마 시스템의 핵심입니다.
Style Dictionary v4의 주요 변화
DTCG 포맷을 실제로 빌드 파이프라인에 연결해주는 도구가 Style Dictionary입니다. Amazon이 초기 개발했고 현재는 커뮤니티 주도로 운영되는 이 오픈소스 빌드 시스템은 v4에서 아키텍처 수준의 변화를 겪었습니다.
| 변화 항목 | v3 이전 | v4 |
|---|---|---|
| 모듈 시스템 | CommonJS | ES Module 완전 전환 |
| 토큰 유형 결정 | CTI 디렉터리 구조에 의존 | $type 프로퍼티 사용 |
| DTCG 지원 | 비공식 | 공식 네이티브 지원 |
| TypeScript | 부분 지원 | 독립적 타입 인터페이스 제공 |
| 실행 환경 | Node.js 전용 | 브라우저에서도 실행 가능 |
CTI(Category/Type/Item): Style Dictionary v3까지 사용되던 디렉터리 기반 토큰 분류 체계입니다.
color/brand/primary같은 경로 구조로 토큰 타입을 암묵적으로 결정했습니다. v4부터는$type필드가 이를 명시적으로 대체하므로, 파일 구조를 자유롭게 가져갈 수 있게 됩니다.
또한 기본 제공 Transform 그룹(css, ios-swift, android) 외에도 커스텀 Transform을 직접 등록할 수 있어, 특수한 출력 포맷이 필요한 경우에도 유연하게 확장이 가능합니다.
실전 적용
예시 1: 기본 크로스플랫폼 파이프라인 구성
프로젝트 구조부터 살펴보겠습니다.
design-tokens/
├── tokens/
│ ├── color.tokens.json
│ ├── spacing.tokens.json
│ └── typography.tokens.json
├── config.mjs
├── package.json
└── dist/
├── web/variables.css
├── ios/StyleDictionary.swift
└── android/res/values/colors.xml토큰 정의 (tokens/color.tokens.json)
{
"color": {
"brand": {
"primary": { "$value": "#0055FF", "$type": "color" },
"secondary": { "$value": "#FF6B00", "$type": "color" }
},
"neutral": {
"100": { "$value": "#F5F5F5", "$type": "color" },
"900": { "$value": "#1A1A1A", "$type": "color" }
}
}
}Style Dictionary v4 설정 (config.mjs)
v4에서는 최상위 source가 전역으로 적용됩니다. 플랫폼별로 다른 소스를 사용하려면 별도 config 파일을 두거나 StyleDictionary.extend()를 활용하는 방식을 권장합니다.
export default {
source: ['tokens/**/*.tokens.json'],
platforms: {
css: {
transformGroup: 'css',
buildPath: 'dist/web/',
files: [{
destination: 'variables.css',
format: 'css/variables'
}]
},
ios: {
transformGroup: 'ios-swift',
buildPath: 'dist/ios/',
files: [{
destination: 'StyleDictionary.swift',
format: 'ios-swift/class.swift'
}]
},
android: {
transformGroup: 'android',
buildPath: 'dist/android/res/values/',
files: [
{ destination: 'colors.xml', format: 'android/colors' },
{ destination: 'dimens.xml', format: 'android/dimens' }
]
}
}
};package.json 스크립트 등록
pnpm 프로젝트라면 빌드 명령어를 scripts에 등록해두는 것이 일관성 면에서 좋습니다.
{
"scripts": {
"build": "style-dictionary build --config config.mjs"
},
"devDependencies": {
"style-dictionary": "^4.0.0"
}
}이후 pnpm add -D style-dictionary로 설치하고, pnpm build로 빌드할 수 있습니다.
플랫폼별 출력 결과 비교
| 플랫폼 | 출력 형식 | 네이밍 컨벤션 | 값 형식 |
|---|---|---|---|
| 웹 CSS | --color-brand-primary |
kebab-case | HEX #0055FF |
| iOS Swift | colorBrandPrimary |
camelCase | UIColor(red:green:blue:alpha:) |
| Android XML | color_brand_primary |
snake_case | ARGB #FF0055FF |
Android ARGB 포맷: Android XML의 색상 표현은
#AARRGGBB순서를 사용합니다.#FF0055FF에서 앞의FF는 알파(불투명도 100%)를 의미합니다. 이는 웹의#RRGGBBAA와 순서가 다르므로, Android 개발 경험이 없는 독자라면 주의가 필요합니다.
/* dist/web/variables.css */
:root {
--color-brand-primary: #0055FF;
--color-brand-secondary: #FF6B00;
}// dist/ios/StyleDictionary.swift
// iOS 팀에서 출력 타입 검토를 권장합니다. SwiftUI 기반 프로젝트라면 Color 타입으로의 출력도 고려해보시면 좋습니다.
public class StyleDictionary {
public static let colorBrandPrimary = UIColor(
red: 0.00, green: 0.33, blue: 1.00, alpha: 1
)
public static let colorBrandSecondary = UIColor(
red: 1.00, green: 0.42, blue: 0.00, alpha: 1
)
}<!-- dist/android/res/values/colors.xml -->
<!-- Android 팀에서 ARGB 포맷 및 리소스 네이밍 검토를 권장합니다 -->
<resources>
<color name="color_brand_primary">#FF0055FF</color>
<color name="color_brand_secondary">#FFFF6B00</color>
</resources>예시 2: 참조 토큰을 활용한 다크 모드 테마
Semantic 계층에 참조 토큰을 사용하면 테마 전환 구현이 크게 단순해집니다.
Primitive 토큰 (tokens/primitive.tokens.json)
{
"palette": {
"blue": {
"500": { "$value": "#0055FF", "$type": "color" },
"600": { "$value": "#0044CC", "$type": "color" }
},
"gray": {
"50": { "$value": "#FAFAFA", "$type": "color" },
"950": { "$value": "#0A0A0A", "$type": "color" }
}
}
}Semantic 토큰 — 라이트 모드 (tokens/semantic-light.tokens.json)
{
"color": {
"background": {
"default": { "$value": "{palette.gray.50}", "$type": "color" },
"surface": { "$value": "#FFFFFF", "$type": "color" }
},
"action": {
"primary": { "$value": "{palette.blue.500}", "$type": "color" }
}
}
}Semantic 토큰 — 다크 모드 (tokens/semantic-dark.tokens.json)
{
"color": {
"background": {
"default": { "$value": "{palette.gray.950}", "$type": "color" },
"surface": { "$value": "#1C1C1C", "$type": "color" }
},
"action": {
"primary": { "$value": "{palette.blue.600}", "$type": "color" }
}
}
}설정에서 테마별 빌드 분기 (config.mjs)
플랫폼별로 다른 소스를 사용해야 하므로 StyleDictionary.extend()를 활용하는 패턴입니다.
import StyleDictionary from 'style-dictionary';
const baseConfig = {
platforms: {
css: {
transformGroup: 'css',
buildPath: 'dist/web/'
}
}
};
const lightSD = new StyleDictionary({
...baseConfig,
source: [
'tokens/primitive.tokens.json',
'tokens/semantic-light.tokens.json'
],
platforms: {
css: {
...baseConfig.platforms.css,
files: [{
destination: 'theme-light.css',
format: 'css/variables',
options: { selector: ':root, [data-theme="light"]' }
}]
}
}
});
const darkSD = new StyleDictionary({
...baseConfig,
source: [
'tokens/primitive.tokens.json',
'tokens/semantic-dark.tokens.json'
],
platforms: {
css: {
...baseConfig.platforms.css,
files: [{
destination: 'theme-dark.css',
format: 'css/variables',
options: { selector: '[data-theme="dark"]' }
}]
}
}
});
await lightSD.buildAllPlatforms();
await darkSD.buildAllPlatforms();실제 출력 결과
/* dist/web/theme-light.css */
:root, [data-theme="light"] {
--color-background-default: #FAFAFA;
--color-background-surface: #FFFFFF;
--color-action-primary: #0055FF;
}/* dist/web/theme-dark.css */
[data-theme="dark"] {
--color-background-default: #0A0A0A;
--color-background-surface: #1C1C1C;
--color-action-primary: #0044CC;
}이 구조에서 다크 모드를 적용할 때 컴포넌트 코드는 전혀 수정하지 않아도 됩니다. <html> 태그에 data-theme="dark" 속성만 토글하면 CSS 변수가 자동으로 교체됩니다.
예시 3: GitHub Actions로 자동 배포 파이프라인 구성
토큰 파일이 변경될 때마다 자동으로 빌드하고 npm 패키지로 배포하는 CI/CD 파이프라인입니다.
# .github/workflows/tokens.yml
name: Build & Publish Design Tokens
on:
push:
paths:
- 'tokens/**/*.tokens.json'
branches:
- main
jobs:
build-and-publish:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v3
with:
version: 9
- uses: actions/setup-node@v4
with:
node-version: '20'
registry-url: 'https://registry.npmjs.org'
- run: pnpm install
- name: Build tokens
run: pnpm build
- name: Bump version (patch)
run: npm version patch --no-git-tag-version
- name: Publish to npm
run: pnpm publish --access public
env:
NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }}버전 관리 주의:
pnpm publish실행 전에 버전을 올리지 않으면 이미 존재하는 버전으로 인해 CI가 실패합니다. 위 예시는 단순한npm version patch를 사용했지만, 팀 규모에 따라 changesets나 semantic-release 같은 체계적인 버전 관리 도구를 도입하는 것을 권장합니다.
각 플랫폼 팀은 npm 패키지를 의존성으로 추가하고, 패키지 버전을 업데이트하면 새 토큰을 즉시 적용할 수 있습니다.
장단점 분석
장점
| 항목 | 내용 |
|---|---|
| 단일 진실 공급원 | 색상 한 줄 변경 시 웹·iOS·Android 전부 자동 반영, 불일치 원천 차단 |
| 멀티 브랜드·테마 | 라이트/다크 모드, 브랜드 변형을 파일 복제 없이 참조 오버라이드로 관리 |
| 수동 핸드오프 제거 | Figma 업데이트 → GitHub 동기화 → 코드 자동 생성으로 디자이너-개발자 간 마찰 최소화 |
| 벤더 종속성 최소화 | W3C DTCG 2025.10 안정 명세 기반이므로 특정 도구에 종속되지 않음 |
| 무한 확장성 | 커스텀 Transform, Format, Preprocessor로 어떤 플랫폼이든 대응 가능 |
| 타입 안전성 | TypeScript 지원으로 토큰 오타·타입 오류를 빌드 시점에 감지 |
단점 및 주의사항
| 항목 | 내용 | 대응 방안 |
|---|---|---|
| 초기 설정 복잡도 | 플랫폼별 Transform 그룹, 네이밍 컨벤션 설계에 상당한 초기 투자 필요 | Primitive → Semantic → Component 3계층 구조를 먼저 설계하고 시작하는 것을 권장 |
| 네이밍 전략 실패 | 토큰 이름 체계가 잘못되면 유지보수가 오히려 어려워짐 | 초기에 네이밍 가이드라인 문서화, 변경 시 자동 마이그레이션 스크립트 준비 |
| 개발자 바이인 부재 | 디자이너-개발자 간 협업 프로세스 없이는 실제 적용률이 낮음 | 팀 전체 온보딩 세션, Storybook 토큰 시각화 문서 자동 생성으로 가시성 확보 |
| 일부 최신 명세 미지원 | 현재 로드맵상 2025.10 명세의 일부 기능은 v4에서 미지원일 수 있으므로 공식 문서 확인을 권장 | v4로 시작하되 DTCG 표준 포맷으로 작성하면 향후 마이그레이션 비용 최소화 |
| 모바일 고유 기능 제약 | iOS Dynamic Color, Android Material You 같은 플랫폼 고유 기능은 별도 처리 필요 | 공통 토큰 외에 플랫폼 전용 오버라이드 레이어 설계 |
실무에서 가장 흔한 실수
-
Primitive 토큰을 컴포넌트에 직접 연결하는 것:
button-bg: {palette.blue.500}처럼 원시값을 바로 쓰면 테마 전환 시 컴포넌트를 일일이 수정해야 합니다. 반드시 Semantic 계층(color-action-primary)을 거쳐 연결하는 구조를 유지하는 것이 좋습니다. -
토큰 과잉 설계: 모든 스타일 속성을 토큰으로 만들려는 경향은 관리 부담만 늘립니다. 플랫폼 간 공유가 필요한 값, 디자이너가 변경할 가능성이 있는 값에만 토큰을 적용하는 것을 권장합니다.
-
네이밍 컨벤션 없이 시작하는 것: 나중에 수백 개의 토큰 이름을 일괄 변경하는 것은 매우 고통스럽습니다.
{카테고리}-{의미}-{변형}-{상태}형태의 네이밍 규칙을 팀이 합의한 뒤 시작하는 것을 강력히 권장합니다.
마치며
지금 팀에서 가장 자주 바뀌는 색상 토큰 하나를 DTCG 포맷으로 작성하는 것만으로도 이 파이프라인의 출발점이 됩니다. 작은 시작이 쌓이면 플랫폼별 불일치가 사라지고, 디자이너의 결정이 코드로 자동 번역되는 구조가 갖춰집니다.
지금 바로 시작해볼 수 있는 3단계:
-
토큰 파일 하나 만들기: 프로젝트에서 가장 자주 바뀌는 색상 5~10개를
tokens/color.tokens.json파일에 DTCG 포맷($value,$type)으로 작성해보시면 됩니다. Style Dictionary Playground에서 브라우저 안에서 바로 테스트해볼 수 있습니다. -
config.mjs 작성 후 빌드 실행:
pnpm add -D style-dictionary로 설치하고, 위 예시의 설정 파일을 복사해pnpm build를 실행해보시면dist/폴더에 CSS, Swift, XML 파일이 자동으로 생성됩니다. -
GitHub Actions 연결: 토큰 파일 변경 시 자동 빌드가 트리거되도록 워크플로 파일을 추가하고, 버전 관리(changesets 또는 semantic-release)와
pnpm publish까지 연결해두면 각 플랫폼 팀이 패키지 버전 업데이트만으로 최신 토큰을 받아갈 수 있는 구조가 완성됩니다.
다음 글: Tokens Studio + Figma Variables를 연동해 디자이너가 Figma에서 색상을 바꾸면 GitHub PR이 자동으로 생성되는 엔드투엔드 파이프라인 구성 방법을 다룰 예정입니다.
참고 자료
- Style Dictionary 공식 문서 | styledictionary.com
- Style Dictionary v4 마이그레이션 가이드 | styledictionary.com
- Style Dictionary DTCG 지원 문서 | styledictionary.com
- W3C Design Tokens Community Group | w3.org
- Design Tokens Format Module 2025.10 공식 명세 | designtokens.org
- DTCG 안정 버전 발표 공식 포스트 | w3.org
- Tokens Studio Style Dictionary 통합 문서 | docs.tokens.studio
- Style Dictionary v4 릴리즈 계획 | tokens.studio
- Martin Fowler: Design Token-Based UI Architecture | martinfowler.com
- Style Dictionary GitHub 레포지토리 | github.com