디자인 토큰 & Variables 운영 전략 — Figma부터 코드 매핑까지 완전 가이드

 

디자이너는 "버튼 색 바꿨어요"라고 했는데 왜 화면에는 열다섯 군데가 다 다른가요? 디자인 토큰이 뭔지, Figma Variables로 어떻게 세팅하고, CSS Custom Properties와 Theme 객체로 코드에 매핑하는지, 그리고 실무에서 동기화가 깨지는 순간을 어떻게 막는지까지 — 이 글 하나로 다 정리해 드릴게요!

 

A clean, modern flat illustration showing a three-layer architecture diagram: bottom layer labeled 'Primitives' with color swatches, middle layer 'Semantics' with role labels (primary, surface, text), top layer 'Components' with a button and card UI. Connecting arrows flow upward. On the right side, a Figma-style variables panel and a code editor showing CSS custom properties. Purple and yellow accent colors, light gray background, professional design system aesthetic.

디자이너와 개발자 사이에 이런 대화, 다들 한 번쯤 겪어보셨을 거예요. "Primary 색상 조금 바꿨어요~" 한 마디에 개발자가 파일 열다섯 개를 열어서 색 코드를 하나씩 찾아 바꾸는 상황. 혹은 반대로, 개발자가 이미 코드에서 색을 바꿨는데 Figma 파일은 아직 옛날 색인 상황. 😅 이게 한두 번이면 모르겠는데, 프로젝트가 커질수록 이 문제는 기하급수적으로 커져요.

디자인 토큰은 바로 이 문제를 해결하기 위해 나온 개념이에요. "색, 간격, 폰트 크기 같은 디자인 결정 사항을 하나의 이름 붙인 변수로 관리하자"는 아이디어인데요, 말은 쉽지만 실제로 Figma Variables와 코드를 동기화해서 운영하는 건 생각보다 손이 많이 가요. 오늘은 그 전체 흐름을 처음부터 끝까지 정리해 드릴게요!

 

디자인 토큰이란 무엇인가요? 🔤

디자인 토큰(Design Token)은 UI를 구성하는 시각적 속성값을 이름 붙인 변수로 추상화한 것이에요. 색상, 타이포그래피, 간격, 그림자, 모션 등 디자인 시스템에서 반복적으로 사용되는 값들이 모두 토큰의 대상이 될 수 있어요.

예를 들어 버튼의 배경색을 #6a1b9a라는 HEX 값으로 직접 쓰는 대신, color.primary.500이라는 토큰 이름으로 참조하는 거예요. 나중에 브랜드 색상이 바뀌어도 토큰 값 하나만 수정하면 그 토큰을 참조하는 모든 컴포넌트가 한꺼번에 업데이트되죠.

토큰의 3단계 계층 구조

실무에서 많이 쓰는 3-tier 구조를 알아두면 토큰 설계가 훨씬 명확해져요.

계층 이름 역할 예시
1계층 Primitive (원시 토큰) 브랜드 팔레트 전체 정의 purple.500 = #6a1b9a
2계층 Semantic (의미 토큰) 용도별 역할 부여 color.action.primary → purple.500
3계층 Component (컴포넌트 토큰) 특정 컴포넌트에 매핑 button.bg.default → color.action.primary
💡 핵심 원칙!
컴포넌트가 Primitive 토큰을 직접 참조하면 안 돼요. 반드시 Semantic 토큰을 거쳐야 해요. 그래야 다크모드 전환이나 테마 교체 시 Semantic 토큰 값만 바꿔도 모든 컴포넌트가 일관되게 업데이트돼요.

 

Figma Variables로 세팅하는 법 🎨

Figma는 2023년 Variables 기능을 정식 출시하면서 디자인 토큰을 Figma 안에서 직접 관리할 수 있게 됐어요. Color, Number, String, Boolean 네 가지 타입을 지원하고, 컬렉션(Collection)과 모드(Mode)로 라이트/다크, 브랜드 테마 전환을 한 파일 안에서 처리할 수 있어요.

📋 Figma Variables 세팅 단계

  1. 컬렉션 생성: 우측 패널 Variables → "+" 클릭 → 컬렉션 이름 지정. Primitives, Semantics, Components 세 컬렉션을 각각 만드는 걸 추천해요.
  2. Primitives 먼저 채우기: 브랜드 팔레트 전체를 purple/100 ~ purple/900 식으로 입력해요. 이 단계에선 실제 HEX 값이 들어가요.
  3. Semantics에서 Primitives 참조: Semantic 토큰 값을 입력할 때 HEX 직접 입력 대신 Variable 아이콘을 클릭해 Primitives 토큰을 연결해요.
  4. Mode 추가 (라이트/다크): Semantics 컬렉션 우측 상단 "Edit Modes"에서 Dark 모드를 추가하고, 같은 토큰 이름에 다른 값(예: 다크모드용 Primitive)을 매핑해요.
  5. 컴포넌트에 적용: 프레임·텍스트·아이콘의 Fill/Stroke/Gap 등을 Color Picker 대신 Variable로 연결하면 모드 전환 시 자동으로 값이 바뀌어요.
⚠️ 주의하세요!
Figma Variables의 Number 타입은 spacing, border-radius, font-size에 사용할 수 있어요. 단, 현재(2025년 기준) Figma Variables는 그림자(Box Shadow) 전체를 하나의 변수로 관리하는 건 지원하지 않아요. 그림자 토큰은 별도 Styles로 관리하거나 플러그인을 활용하는 편이에요.

 

코드로 매핑하기 — CSS Variables & Theme 객체 🖥️

Figma에서 잘 정리한 토큰을 코드로 옮길 때는 두 가지 방식이 주로 쓰여요. CSS Custom Properties(CSS 변수)와 JS/TS Theme 객체예요. 프로젝트 스택에 따라 선택하거나 둘 다 함께 쓰기도 해요.

① CSS Custom Properties

가장 범용적인 방식이에요. 프레임워크에 종속되지 않고, 런타임에서도 값을 바꿀 수 있어서 다크모드 구현에 특히 유리해요.

📝 Primitive → Semantic → Component 레이어 예시

/* 1. Primitives — 실제 값 */
:root {
  --purple-500: #6a1b9a;
  --purple-200: #ce93d8;
  --gray-100: #f5f5f5;
  --gray-900: #212121;
}

/* 2. Semantics — 역할 부여 (라이트 기본) */
:root {
  --color-action-primary: var(--purple-500);
  --color-bg-surface: var(--gray-100);
  --color-text-default: var(--gray-900);
}

/* 다크모드 — Semantic 값만 교체 */
[data-theme="dark"] {
  --color-action-primary: var(--purple-200);
  --color-bg-surface: var(--gray-900);
  --color-text-default: var(--gray-100);
}

/* 3. Component — Semantic 참조 */
.btn-primary {
  background-color: var(--color-action-primary);
  color: var(--color-bg-surface);
}

② JS/TS Theme 객체 (React 예시)

Styled-components, Emotion, MUI 같은 CSS-in-JS 환경이나 Tailwind의 theme extend를 쓸 때는 JS 객체로 토큰을 관리해요. 타입 안전성이 생기고, 자동완성이 돼서 개발 경험(DX)이 좋아요.

// tokens/primitives.ts
export const primitives = {
  purple: { 200: '#ce93d8', 500: '#6a1b9a' },
  gray: { 100: '#f5f5f5', 900: '#212121' },
};

// tokens/themes.ts
import { primitives as p } from './primitives';

export const lightTheme = {
  color: {
    action: { primary: p.purple[500] },
    bg: { surface: p.gray[100] },
    text: { default: p.gray[900] },
  },
};

export const darkTheme = {
  ...lightTheme,
  color: {
    action: { primary: p.purple[200] },
    bg: { surface: p.gray[900] },
    text: { default: p.gray[100] },
  },
};
📌 CSS Variables vs Theme 객체 — 언제 뭘 쓸까요?
CSS Variables는 런타임 교체가 쉽고 프레임워크 무관. 다크모드를 data-theme 속성 하나로 처리하고 싶을 때 최적이에요. Theme 객체는 TypeScript 타입 안전성·자동완성이 필요하고, CSS-in-JS 환경에서 컴포넌트 단위로 토큰을 소비할 때 유리해요. 둘을 함께 쓰는 것도 가능해요 — Theme 객체를 CSS Variables로 변환해주는 유틸 함수를 만들면 돼요.

 

실무에서 동기화가 깨지는 순간들 — 그리고 해결법 ⚠️

이론은 깔끔한데 실무에서는 Figma와 코드 사이 동기화가 생각보다 자주 어긋나요. 제가 직접 겪었거나 팀에서 자주 보던 패턴 네 가지와 각각의 해결법을 솔직하게 정리해 드릴게요.

💥 시나리오 1 — "Figma에서 토큰 이름 바꿨는데 코드는 그대로"

원인: 디자이너가 Figma Variables 이름을 변경했지만 코드의 CSS 변수명은 수동으로 업데이트하지 않은 경우.

해결: 토큰 이름 변경은 반드시 코드 리뷰 없이 단독으로 하지 않는다는 팀 규칙을 만들고, Style Dictionary나 Token Studio 같은 도구로 Figma → 코드 변환을 자동화해요. 이름 변경 = PR 필수.

💥 시나리오 2 — "개발자가 하드코딩으로 급하게 패치"

원인: 긴급 수정 상황에서 토큰 참조 없이 HEX 값을 직접 입력. Figma는 여전히 토큰 참조 상태지만 코드만 다른 값.

해결: ESLint custom rule이나 Stylelint 규칙으로 하드코딩 색상값 사용 시 경고를 띄우도록 설정해요. no-hardcoded-colors 같은 커스텀 룰이 효과적이에요.

💥 시나리오 3 — "Figma Variables 업데이트가 코드에 자동 반영 안 됨"

원인: 토큰을 수동으로 양쪽에서 관리하다 보니 어느 순간부터 파일 두 벌이 독립적으로 진화하는 현상.

해결: 단일 진실의 원천(Single Source of Truth)을 정해요. 코드의 토큰 JSON 파일을 원본으로 두고, Figma에 역으로 임포트하는 방식(Tokens Studio 플러그인의 GitHub 동기화 기능)이 실무에서 가장 안정적이에요.

💥 시나리오 4 — "새 디자이너가 Primitive 토큰을 컴포넌트에 직접 연결"

원인: 계층 구조 원칙을 모르는 팀원이 편의상 purple.500을 버튼에 직접 연결. 다크모드 추가 시 해당 버튼만 테마 전환이 안 됨.

해결: 토큰 사용 가이드를 Figma 내 커버 페이지나 Notion 위키에 명문화하고, Primitive 컬렉션의 Publish 설정에서 외부 공개를 제한해 Semantic을 통해서만 쓸 수 있도록 강제해요.

 

핵심 내용 정리 📝

오늘 다룬 내용을 한 번 더 정리해 드릴게요!

  1. 디자인 토큰: 색상·간격·폰트 등 디자인 결정값을 이름 붙인 변수로 추상화. 변경 비용을 극적으로 낮춰줘요.
  2. 3계층 구조: Primitive → Semantic → Component 순으로, 컴포넌트는 반드시 Semantic을 통해 값을 참조해야 해요.
  3. Figma Variables: 컬렉션과 Mode로 라이트/다크 테마를 한 파일에서 관리. Semantic이 Primitive를 참조하도록 연결하는 게 핵심.
  4. 코드 매핑: CSS Custom Properties는 런타임 테마 전환에 유리, Theme 객체는 타입 안전성과 DX에 유리. 프로젝트 스택에 맞게 선택하거나 함께 써요.
  5. 동기화 전략: 단일 진실의 원천 확보(코드 토큰 JSON → Figma 임포트), 하드코딩 린트 룰, 이름 변경 PR 필수화 — 이 세 가지가 동기화 붕괴를 막는 핵심이에요.

 

🎨

디자인 토큰 운영 핵심 요약 카드

🏗️ 토큰 3계층 흐름:
Primitive (원시값) Semantic (역할) Component (컴포넌트)
🎛️ Figma Variables: 컬렉션 3개 분리 + Mode로 라이트/다크 관리. Semantic이 Primitive를 참조하게 연결.
💻 코드 전략: CSS Custom Properties(런타임 테마 전환) 또는 Theme 객체(타입 안전성) — 스택에 따라 선택.
🔒 동기화 수호 3원칙: 단일 진실의 원천 확보 · 하드코딩 린트 룰 · 이름 변경 = PR 필수

 

자주 묻는 질문 ❓

Q: Figma Variables와 Styles는 어떻게 다른가요? 둘 다 써야 하나요?
A: Variables는 단일 값(색상 하나, 숫자 하나)을 저장하고 Modes로 테마 전환이 가능해요. Styles는 여러 속성을 묶은 집합(예: Text Style = font-family + size + weight + line-height)이에요. 지금은 Variables로 색·크기 토큰을 관리하고, 타이포그래피처럼 여러 속성이 묶인 건 Styles로 관리하는 혼용 방식이 가장 현실적이에요.
Q: Token Studio와 Style Dictionary 중 어떤 걸 써야 하나요?
A: Token Studio(Figma 플러그인)는 Figma 내에서 토큰을 편집하고 GitHub에 JSON으로 동기화하는 데 특화돼 있어요. 디자이너 친화적이에요. Style Dictionary(아마존 오픈소스)는 코드 빌드 파이프라인에서 토큰 JSON을 CSS, JS, iOS, Android 등 다양한 포맷으로 변환해 주는 도구예요. 둘을 함께 쓰는 게 가장 강력해요 — Token Studio로 Figma↔GitHub 동기화, Style Dictionary로 코드 포맷 변환.
Q: 소규모 프로젝트에서도 토큰 3계층이 필요한가요?
A: 솔직히 말하면, 혼자 또는 2인 팀의 단기 프로젝트에서 3계층을 모두 갖추는 건 오버킬일 수 있어요. 이 경우 Primitive 없이 Semantic 토큰만으로 단순하게 시작하고, 프로젝트가 커지면 Primitive를 분리하는 방향으로 점진적으로 확장하는 게 더 현실적이에요.
Q: 다크모드 말고 멀티 브랜드 테마도 같은 구조로 관리할 수 있나요?
A: 네! 이게 3계층 구조의 진가예요. Figma Variables에서 Mode를 "Light, Dark, Brand-A, Brand-B"로 추가하고, 각 모드에서 Semantic 토큰이 다른 Primitive를 가리키도록 설정하면 돼요. 코드에서는 data-theme="brand-a" 속성만 바꿔도 전체 브랜드가 전환되는 구조를 만들 수 있어요.
Q: 토큰 이름 규칙(naming convention)은 어떻게 정하는 게 좋나요?
A: 일반적으로 [카테고리].[역할].[상태].[스케일] 패턴을 많이 써요. 예: color.feedback.error.default. 중요한 건 팀 안에서 규칙이 일관되는 거예요. W3C의 Design Tokens Community Group(DTCG)에서 발표한 토큰 표준 스펙을 참고하면 업계 표준에 가까운 네이밍을 잡을 수 있어요.

처음 디자인 토큰 개념을 접하면 "이렇게까지 해야 해?" 싶을 수 있어요. 근데 프로젝트가 커지고 팀원이 늘어나면, 토큰 없이 작업했을 때의 그 끔찍한 파편화를 경험하고 나서야 "왜 진작에 안 했지?" 하게 돼요. 😄 처음엔 Semantic 토큰 몇 개부터 시작해보는 것도 충분해요. 작은 것부터 시작해서 점진적으로 3계층을 완성해 나가는 게 현실적인 방법이에요. 궁금한 점이나 팀에서 겪은 동기화 고민이 있으면 댓글로 편하게 공유해 주세요! 🙌