
디자인 시스템을 Figma에서 토큰으로 관리하면 장점이 많다.
text-primary, bg-brand 같은 semantic token으로 코딩문제는 “토큰을 어떻게 코드로 가져오느냐”인데,
여기서는 Tokens Studio export JSON(tokens.json)을 입력으로 받아서:
styles/design-tokens.ts (타입 안전 + 앱에서 사용)styles/design-tokens.css (CSS 변수 + 유틸리티 클래스)를 자동 생성한다.
예시 폴더 구조:
src/
token/
sync-tokens-studio.ts
styles/
design-tokens.ts (자동 생성)
design-tokens.css (자동 생성)
tokens.json (Tokens Studio export)
package.json scripts:
{
"scripts": {
"sync:tokens": "node --import tsx src/token/sync-tokens-studio.ts"
}
}
실행:
pnpm sync:tokens
Tokens Studio에서 Export할 때 권장:
tokens.json 저장스크립트는 입력 파일 후보 경로를 여러 개 탐색하도록 만들어둠
(tokens.json,../tokens.json,../../tokens.json등)
kebab-case로 통일space-16, font-size-12)text-primary, bg-brand)스크립트의 normalizeKey()가 이 역할을 한다.
function normalizeKey(key: string): string {
return key
.replace(/[/:]/g, '-')
.replace(/\s+/g, '-')
.replace(/[()]/g, '')
.replace(/-+/g, '-')
.replace(/^-|-$/g, '')
.toLowerCase();
}
Tokens Studio 토큰은 아래처럼 서로 참조하는 형태가 많다.
{
"primary": { "$value": "{purple.500}", "$type": "color" }
}
이를 실제 값으로 풀어주는 로직이 resolveReference().
{...} 형태면 경로를 찾아 실제 값을 반환Primitive: raw color 값(팔레트)
예: neutral.100, purple.500
Semantic: 의미 기반(개발에서 주로 사용)
예: text.primary, bg.brand, border.primary
결과 타입은 아래처럼 구성:
interface TailwindTokens {
colors: {
primitive: NestedColorMap;
text: NestedColorMap;
bg: NestedColorMap;
border: NestedColorMap;
icon: NestedColorMap;
};
spacing: Record<string, string>;
container: Record<string, string>;
fontSize: Record<string, string>;
lineHeight: Record<string, string>;
fontWeight: Record<string, string>;
letterSpacing: Record<string, string>;
borderRadius: Record<string, string>;
borderWidth: Record<string, string>;
typography: {
heading: Record<string, TypographyPreset>;
body: Record<string, TypographyPreset>;
};
}
Tokens Studio의 number / dimension 타입을 CSS 값으로 만든다.
px를 붙임unit: null로 숫자 그대로 유지function formatDimensionValue(value: string | number, options: { unit?: string | null } = {}) {
if (typeof value === 'number') {
if (options.unit === null) return String(value);
return `${value}${options.unit ?? 'px'}`;
}
// ...
}
Semantic Typography를 프리셋으로 뽑아서
.text-heading-4xl 같은 클래스를 자동 생성한다.
예:
.text-heading-4xl {
font-size: 56px;
line-height: 68px;
letter-spacing: -0.2px;
font-weight: 600;
}
이 방식은 “tailwind config에 다 때려넣기”보다:
designTokens 전체 exporttextColors, bgColors 등) 분리 exporttailwindColors 제공export const tailwindColors = {
text: textColors,
bg: bgColors,
border: borderColors,
icon: iconColors,
...primitiveColors,
};
앱 코드에서 사용할 수도 있고:
import { textColors } from '@/styles/design-tokens';
const primary = textColors.primary; // "#18181b"
CSS 변수 + 유틸리티 클래스가 함께 생성된다.
--color-text-primary--spacing-space-16.text-body-md, .container-960, .space-16 등예:
@theme inline {
--color-text-primary: #18181b;
--spacing-space-16: 16px;
}
Tailwind v4를 쓰는 경우
@theme inline컨셉과 잘 맞는다.
@import "./design-tokens.css";
프로젝트 구조에 따라:
src/styles/design-tokens.css면 경로만 맞춰주면 됨<h1 className="text-heading-3xl">제목</h1>
<p className="text-body-md">본문</p>
gap)<div className="flex space-16">
<div>left</div>
<div>right</div>
</div>
<div className="container-960 mx-auto">
...
</div>
여기서
.container-960은width:100%+max-width: 960px로 생성됨
실제 토큰을 보면 sucess, transeparent, nomal 같은 키가 있다.
(스크립트는 그대로 kebab-case로 변환해서 내보내기 때문에)
실행 로그에 이런 게 뜨면:
⚠️ 미해석 참조:
- {foo.bar}
TokenStudio / Primitive: Color/Mode 1)가 바뀌지 않았는지를 확인한다.
토큰이 10개 미만이면 구조가 바뀐 가능성이 높다.
transformTokens()에서 찾는 키 경로를 최신 구조에 맞게 업데이트이 방식의 핵심은 하나다.
Figma 토큰을 소스 오브 트루스로 두고
코드는 자동 생성물로만 관리한다.
이렇게 해두면:
tokens.json export 반영pnpm sync:tokensstyles/design-tokens.ts, styles/design-tokens.css 변경 포함원하면 다음 글로 이어서:
tailwind.config(또는 Tailwind v4 세팅)에 토큰을 더 깊게 연동하는 방법같은 것도 정리해줄게.