Claude Code로 Design Tokens 구현하기: Figma 핸드오프부터 CSS 변수와 Tailwind까지
Claude Code로 Design Tokens를 설계하고 Style Dictionary, CSS 변수, Tailwind, React에 연결합니다.
Design Tokens는 UI 변경을 위한 계약이다
Design Tokens는 색상, 간격, 글꼴 크기, radius, shadow 같은 디자인 결정을 이름이 있는 데이터로 관리하는 방식이다. 컴포넌트마다 #2563eb, 16px, 0.5rem을 직접 쓰는 대신 tokens.json에 원본 값을 두고 CSS 변수, Tailwind 설정, React 컴포넌트가 그 값을 사용하게 만든다.
Claude Code는 이 작업에 잘 맞는다. 기존 CSS를 읽고 반복되는 하드코딩 값을 찾고, 의미 있는 token 이름을 제안하고, 컴포넌트를 수정한 뒤 빌드와 테스트를 실행할 수 있기 때문이다. 단, “UI를 일관되게 만들어줘”처럼 넓은 요청은 위험하다. token 원본, 수정할 컴포넌트 범위, 접근성 기준, 실행할 명령을 함께 지정해야 한다.
함께 읽으면 좋은 글은 Claude Code 디자인 시스템 구축과 Claude Code 접근성 개선이다. 공식 문서는 안정판 Design Tokens Format Module 2025.10, Claude Code docs, Figma Variables plugin API, Tailwind theme docs, Style Dictionary docs, MDN CSS custom properties, WCAG contrast guidance를 기준으로 삼는다.
raw token과 semantic token
raw token은 재료를 설명한다. 예를 들면 color.blue.600, space.4, font.size.base다. semantic token은 쓰임새를 설명한다. 예를 들면 color.action.primary.bg, color.text.muted, color.surface.default다. component token은 특정 컴포넌트의 세부 규칙이다. 예를 들면 button.primary.paddingX다.
| 종류 | 예시 | 용도 |
|---|---|---|
| raw | color.blue.600 | 팔레트, spacing scale, typography scale |
| semantic | color.action.primary.bg | 제품 의미, 테마 전환, 다크 모드 |
| component | button.primary.paddingX | 컴포넌트 전용 예외가 필요할 때 |
처음에는 raw와 semantic만으로 시작하는 편이 좋다. 버튼이 color.blue.600을 직접 쓰면 브랜드 색이 바뀌었을 때 이름이 거짓말을 한다. color.action.primary.bg를 쓰면 값은 바뀌어도 목적은 유지된다.
Figma-like spec을 코드 계약으로 바꾸기
Figma Variables는 유용하지만 Claude Code에는 원본 이름 덤프가 아니라 리뷰 가능한 handoff를 주는 편이 안전하다. Figma-like spec은 원본 파일, 모드, 요청 변경, 영향 컴포넌트, 리뷰 규칙을 적는다. 이렇게 하면 Blue / 600 같은 시각적 이름이 React에 들어가고, 제품에서는 사실 color.action.primary.bg가 필요했던 risk를 줄일 수 있다.
{
"figmaSource": {
"file": "Marketing UI Kit",
"collection": "Brand v2",
"modes": ["light", "dark"]
},
"changeRequest": {
"type": "replace-token",
"from": "color.blue.600",
"to": "color.action.primary.bg",
"components": ["Button", "Link", "Card"]
},
"reviewRules": [
"Do not use raw color tokens in component CSS.",
"Keep focus and hover tokens explicit.",
"List affected use case and pitfall before editing files."
]
}
Claude Code에는 먼저 영향 표를 만들게 하고 리뷰 후에 편집을 맡긴다. 이 workflow는 토큰 하나가 Button, Link, Card, focus outline을 동시에 바꾸는 pitfall을 초기에 잡아낸다.
실행 가능한 tokens.json
다음을 tokens/tokens.json으로 저장한다.
{
"color": {
"blue": {
"50": { "$type": "color", "$value": "#eff6ff" },
"600": { "$type": "color", "$value": "#2563eb" },
"700": { "$type": "color", "$value": "#1d4ed8" }
},
"slate": {
"50": { "$type": "color", "$value": "#f8fafc" },
"100": { "$type": "color", "$value": "#f1f5f9" },
"700": { "$type": "color", "$value": "#334155" },
"900": { "$type": "color", "$value": "#0f172a" }
},
"white": { "$type": "color", "$value": "#ffffff" },
"focus": { "$type": "color", "$value": "#f59e0b" },
"surface": {
"default": { "$type": "color", "$value": "{color.white}" },
"muted": { "$type": "color", "$value": "{color.slate.50}" },
"inverse": { "$type": "color", "$value": "{color.slate.900}" }
},
"text": {
"default": { "$type": "color", "$value": "{color.slate.900}" },
"muted": { "$type": "color", "$value": "{color.slate.700}" },
"inverse": { "$type": "color", "$value": "{color.white}" }
},
"action": {
"primary": {
"bg": { "$type": "color", "$value": "{color.blue.600}" },
"bgHover": { "$type": "color", "$value": "{color.blue.700}" },
"text": { "$type": "color", "$value": "{color.white}" }
}
}
},
"dark": {
"color": {
"surface": {
"default": { "$type": "color", "$value": "{color.slate.900}" },
"muted": { "$type": "color", "$value": "{color.slate.700}" }
},
"text": {
"default": { "$type": "color", "$value": "{color.white}" },
"muted": { "$type": "color", "$value": "{color.slate.100}" }
},
"action": {
"primary": {
"bg": { "$type": "color", "$value": "{color.blue.50}" },
"bgHover": { "$type": "color", "$value": "{color.white}" },
"text": { "$type": "color", "$value": "{color.slate.900}" }
}
}
}
},
"space": {
"2": { "$type": "dimension", "$value": "0.5rem" },
"3": { "$type": "dimension", "$value": "0.75rem" },
"4": { "$type": "dimension", "$value": "1rem" },
"6": { "$type": "dimension", "$value": "1.5rem" }
},
"font": {
"size": {
"sm": { "$type": "dimension", "$value": "0.875rem" },
"base": { "$type": "dimension", "$value": "1rem" },
"lg": { "$type": "dimension", "$value": "1.125rem" }
},
"weight": {
"medium": { "$type": "fontWeight", "$value": "500" },
"bold": { "$type": "fontWeight", "$value": "700" }
}
},
"radius": {
"md": { "$type": "dimension", "$value": "0.5rem" },
"lg": { "$type": "dimension", "$value": "0.75rem" }
},
"shadow": {
"button": { "$type": "shadow", "$value": "0 1px 2px rgb(15 23 42 / 0.16)" }
}
}
Style Dictionary로 CSS 변수 생성
npm install --save-dev style-dictionary
style-dictionary.config.js를 만든다.
export default {
source: ["tokens/tokens.json"],
hooks: {
formats: {
"css/variables-with-dark": ({ dictionary }) => {
const light = dictionary.allTokens
.filter((token) => !token.path.includes("dark"))
.map((token) => ` --${token.name}: ${token.value};`)
.join("\n");
const dark = dictionary.allTokens
.filter((token) => token.path[0] === "dark")
.map((token) => ` --${token.path.slice(1).join("-")}: ${token.value};`)
.join("\n");
return `:root {\n${light}\n}\n\n[data-theme="dark"] {\n${dark}\n}\n`;
}
}
},
platforms: {
css: {
transformGroup: "css",
buildPath: "src/styles/",
files: [{ destination: "tokens.css", format: "css/variables-with-dark" }]
}
}
};
package.json에는 다음 스크립트를 추가한다.
{
"scripts": {
"tokens:build": "style-dictionary build --config style-dictionary.config.js"
}
}
생성되는 CSS는 다음과 같은 형태다.
:root {
--color-action-primary-bg: #2563eb;
--color-action-primary-bg-hover: #1d4ed8;
--color-action-primary-text: #ffffff;
--space-3: 0.75rem;
--space-4: 1rem;
--font-size-base: 1rem;
--font-weight-bold: 700;
--radius-md: 0.5rem;
--shadow-button: 0 1px 2px rgb(15 23 42 / 0.16);
}
[data-theme="dark"] {
--color-surface-default: #0f172a;
--color-text-default: #ffffff;
--color-action-primary-bg: #eff6ff;
--color-action-primary-text: #0f172a;
}
CSS custom properties는 브라우저 표준 변수다. data-theme="dark"를 html이나 body에 붙이면 같은 컴포넌트가 다크 테마 값을 사용한다.
Tailwind에 연결
/** @type {import('tailwindcss').Config} */
module.exports = {
content: ["./src/**/*.{ts,tsx,js,jsx,mdx}"],
theme: {
extend: {
colors: {
surface: { DEFAULT: "var(--color-surface-default)", muted: "var(--color-surface-muted)" },
text: { DEFAULT: "var(--color-text-default)", muted: "var(--color-text-muted)", inverse: "var(--color-text-inverse)" },
action: { primary: "var(--color-action-primary-bg)", "primary-hover": "var(--color-action-primary-bg-hover)" }
},
spacing: { 3: "var(--space-3)", 4: "var(--space-4)", 6: "var(--space-6)" },
borderRadius: { md: "var(--radius-md)", lg: "var(--radius-lg)" },
boxShadow: { button: "var(--shadow-button)" }
}
}
};
React Button에서 사용
import "./Button.css";
type ButtonProps = {
children: React.ReactNode;
onClick?: () => void;
disabled?: boolean;
};
export function Button({ children, onClick, disabled = false }: ButtonProps) {
return (
<button className="Button" onClick={onClick} disabled={disabled}>
{children}
</button>
);
}
.Button {
background: var(--color-action-primary-bg);
border: 0;
border-radius: var(--radius-md);
box-shadow: var(--shadow-button);
color: var(--color-action-primary-text);
cursor: pointer;
font-size: var(--font-size-base);
font-weight: var(--font-weight-bold);
padding: var(--space-3) var(--space-4);
}
.Button:hover:not(:disabled) {
background: var(--color-action-primary-bg-hover);
}
.Button:focus-visible {
outline: 3px solid var(--color-focus);
outline-offset: 2px;
}
.Button:disabled {
cursor: not-allowed;
opacity: 0.55;
}
실제 사용 사례와 함정
첫 번째 사용 사례는 Figma에서 코드로 넘기는 작업이다. Figma Variables는 좋은 입력이지만 최종 계약은 tokens.json이어야 한다. Claude Code에는 Figma export와 현재 token 파일을 비교하고 추가, 삭제, 이름 변경, 영향받는 컴포넌트를 표로 정리하게 한다.
두 번째는 다크 모드다. 컴포넌트 CSS를 복사하지 말고 surface, text, action 같은 semantic token만 덮어쓴다.
세 번째는 리브랜딩이나 랜딩 페이지 개편이다. token build와 Storybook 스크린샷을 같이 쓰면 색상과 간격이 화면마다 조금씩 달라지는 drift를 막을 수 있다.
네 번째는 PR 리뷰다. Claude Code는 raw color 직접 사용, 생성 CSS 수동 수정, focus 상태 누락, WCAG AA 4.5:1 대비 미달을 빠르게 찾아낼 수 있다.
주의할 점도 분명하다. 첫날부터 component token을 너무 많이 만들지 말 것. blueButton처럼 외형만 담은 이름을 피할 것. 생성된 tokens.css를 직접 수정하지 말 것. 접근성 대비 검사를 마지막으로 미루지 말 것.
Review checklist:
-
tokens/tokens.json만 직접 수정했고 생성된 CSS는 손으로 고치지 않았다. - Button, Link, Card를 light, dark, hover, disabled, focus 상태에서 확인했다.
- React와 Tailwind가 raw palette token이 아니라 semantic token을 사용한다.
- PR에 Figma-like spec, use case, pitfall, risk, workflow가 적혀 있다.
- WCAG 2.2 AA 대비를 텍스트와 인터랙션 상태에서 확인했다.
Claude Code 리뷰 프롬프트
Design token review task:
- Read tokens/tokens.json, style-dictionary.config.js, src/styles/tokens.css, tailwind.config.js, and src/components/Button.tsx.
- Check that components use semantic tokens, not raw color tokens.
- Verify light and dark theme values for button, surface, and text tokens.
- Flag any generated CSS file that was edited manually.
- Check WCAG 2.2 AA contrast for normal text and button text.
- Suggest the smallest safe diff. Do not rename tokens unless you list every affected component.
- After changes, run npm run tokens:build and the focused component tests.
마무리
Design Tokens는 Figma, CSS, Tailwind, React, 접근성, 코드 리뷰를 같은 언어로 묶는다. 실무에서는 tokens.json을 원본으로 두고, Style Dictionary로 CSS 변수를 생성하고, 컴포넌트에서는 semantic token만 사용하고, PR에서 drift와 contrast를 확인하는 흐름이 가장 현실적이다.
이 글의 예시는 token 파일에서 CSS 변수를 생성하고 React Button의 배경색, 글자색, 간격, radius, shadow, focus outline에 연결하는 방식으로 확인했다. 재사용 가능한 프롬프트와 체크리스트는 ClaudeCodeLab 제품을 참고하면 좋다. 운영 환경에서는 Storybook 상태, axe 검사, 스크린샷 리뷰를 더하고, 팀 도입이 필요하다면 ClaudeCodeLab 교육 및 상담을 참고하라.
무료 PDF: Claude Code 치트시트
이메일을 입력하면 명령, 리뷰 습관, 안전한 워크플로를 정리한 PDF를 받을 수 있습니다.
개인정보를 안전하게 관리하며 스팸을 보내지 않습니다.
작성자 소개
Masa
Claude Code 실무 워크플로와 팀 도입을 검증하는 엔지니어입니다.
관련 글
Obsidian 메모를 CLAUDE.md로 바꾸는 Claude Code 워크플로
Obsidian 작업 메모를 CLAUDE.md 운영 노트로 정리해 Claude Code 세션의 문맥 반복을 줄입니다.
Claude Code Revenue CTA Routing: 글에서 PDF, Gumroad, 상담으로 보내기
독자 의도에 따라 무료 PDF, Gumroad 상품, 상담으로 나누는 Claude Code CTA 설계입니다.
Claude Code 팀 인계 규칙: 리뷰 증거, 권한, 롤백, 수익 경로까지 넘기는 법
Claude Code 작업을 팀에 넘길 때 필요한 증거, 권한 규칙, 롤백, 무료 PDF, Gumroad, 상담 경로 체크리스트.