SuperAPI AI Directory

design-tokens-transformer

Design & creation

View repository

Updated 10-15 23:23

About this skill

design-tokens-transformer — 3-layer token bidirectional conversion

Overview

Brand tokens require different forms for each consumer. This skill encodes and converts the **same brand truth (color, typography, spacing, radius, shadow, motion)** across 3 layers. L1 (DTCG) is the SSOT, L2/L3 are derivatives, and consistency between layers is guaranteed through reverse extraction and roundtrip checks.

This SKILL.md includes everything: the 3-layer schema, bidirectional conversion rules, roundtrip checks, and usage examples. Color and font values are expressed as brand-agnostic placeholders (#RRGGBB, <BrandFont>), and actual brand values are injected from the conversion input (DTCG source).

Trigger keywords

DTCG token conversion, 3-layer token mapping, CSS custom properties generation, shadcn semantic roles, Tailwind v4 @theme, token roundtrip, design tokens transformer

3-layer model

Layer Form Consumer Representative files (handoff.zip)
L1 — DTCG SSOT W3C Design Tokens ($value/$type/$description), hierarchical groups Brand source of truth assets/round3/02-tokens.json
L2 — Primitive CSS variables --color-* · --neutral-50..950 · --tracking-* + self-host @font-face Single-file HTML render / card colors_and_type.css
L3 — semantic/shadcn --base-background · --base-primary · --base-ring · --base-sidebar-* · --base-chart-1..5 + Tailwind v4 @theme + .dark switching Production code (shadcn system) assets/round3/globals.css

L1 — DTCG SSOT schema

  • W3C format: each token holds $value / $type / $description has
  • Hierarchical groups: color.{brand,neutral,semantic,dark,gradient} · typography.{family,weight,size,lineHeight,letterSpacing} · spacing · radius · shadow · motion.{duration,easing} · container · logo
  • $descriptionrecords provenance and meaning → brand source of truth

Minimum token set per DTCG group

Group Minimum tokens Notes
color.brand primary, ink, background (+ primary-hover/active, surface) Primary color, body text color, and background — 3 types are the required minimum
color.neutral 50·100·200·300·400·500·600·700·800·900·950 11-step luminance scale
color.semantic success, warning, danger, info 4 state colors
color.dark background, surface, primary, foreground, border Minimum dark override set
color.gradient signature (+ soft/dark derivatives) Signature asset
typography.family sans (+ latin/mono/serif) Body font required
typography.weight regular(400), bold(700) (+ medium/semibold/black) fontWeight
typography.size xs…6xl + display rem/clamp
typography.lineHeight tight, normal (+ snug/relaxed) number
typography.letterSpacing display, heading, body, caption Korean letter-spacing encoding
spacing 0…32 (4px base step) dimension
radius none, sm, md, lg, full (+ xl/pill) dimension
shadow sm, md, lg (+ xs/xl/signature) shadow
motion.duration fast, normal (+ instant/slow/page) duration
motion.easing default (+ bounce/smooth) cubicBezier
container·logo (Optional) breakpoints and asset paths logo는 asset type

$type allowed values

$type Value form Standard
color hex · rgb() · oklch() W3C
dimension rem · px · em · clamp() W3C
fontFamily Font stack string W3C
fontWeight 100–900 integer W3C
number Unitless number (line-height, etc.) W3C
duration 150ms · 0.4s W3C
cubicBezier cubic-bezier(…) W3C
shadow box-shadow string/object W3C(composite)
gradient linear-gradient(…) string W3C (composite) — bundles use the string shorthand form
asset File path Bundle extension (non-standard) — assets such as logos

Example JSON (brand-agnostic — placeholders)

{
  "$schema": "https://design-tokens.github.io/community-group/format/",
  "$description": "<브랜드> Design Tokens — <출처 가이드라인> 흡수",
  "$version": "1.0.0",
  "$updatedAt": "YYYY-MM-DD",
  "color": {
    "brand": {
      "primary": { "$value": "#RRGGBB", "$type": "color",
        "$description": "<출처> 명시 — 브랜드 주색. 타이틀/CTA/아이콘" },
      "ink":     { "$value": "#RRGGBB", "$type": "color",
        "$description": "본문 텍스트 — #000 대체" }
    },
    "neutral": { "500": { "$value": "#RRGGBB", "$type": "color" } }
  },
  "typography": {
    "letterSpacing": {
      "display-tight": { "$value": "-0.075em", "$type": "dimension",
        "$description": "히어로 강조" },
      "body":          { "$value": "-0.025em", "$type": "dimension" }
    }
  }
}

$descriptionis the place that stamps **provenance and meaning** — by recording in the token which guideline/system the value came from, it makes L1 the source of truth.

L2 — Primitive CSS variable schema

  • --color-primary / --color-ink / --color-bg / --color-surface
  • --neutral-50 … --neutral-950 (luminance scale)
  • --tracking-display-tight Letter-spacing variables such as (Korean letter-spacing rule encoding)
  • self-host @font-face (brand font) + CDN fallback font

CSS variable naming convention

Category Prefix token 예
Color role --color-<역할> --color-primary·--color-ink·--color-bg·--color-surface
Luminance scale --neutral-<50..950> --neutral-500
Foreground --fg-<1..3>·--fg-on-primary Body/secondary/placeholder
Border --border-<1|2|strong>·--border-focus-ring
Gradient --gradient-<이름> (+-soft/-dark) --gradient-signature
Font/weight --font-<sans|latin|mono>·--fw-<regular..black>
Letter-spacing --tracking-<역할> --tracking-body
Size/line-height --text-<xs..6xl|display>·--lh-<tight..relaxed>
Spacing/radius/shadow --space-<0..32>·--radius-<none..full>·--shadow-<xs..xl|signature>
Motion/container --duration-<…>·--easing-<…>·--container-<sm..2xl>
  • Dark mode: L2 only in the [data-theme="dark"] scope redefines values only (variable names identical).
  • Aliases (bg←background, fw←weight, tracking←letterSpacing, space←spacing, lh←lineHeight) may only use those registered in the table — no arbitrary abbreviations.

@font-face block template (self-host)

/* 굵기 1개당 @font-face 1개 반복 */
@font-face {
  font-family: "<BrandFont>";
  font-weight: 400;                 /* 각 굵기별로 반복 (100~900) */
  font-style: normal;
  font-display: swap;               /* FOIT 방지 */
  src: url("./fonts/<BrandFont>-Regular.otf") format("opentype");
}
/* 보조 라틴/모노 폰트는 CDN @import */
@import url("https://fonts.googleapis.com/css2?family=<Latin>:wght@400;600;700&display=swap");

Letter-spacing variable mapping table (Korean typography encoding)

Letter-spacing variable 값 Application Naming (−N)
--tracking-display-tight -0.075em Hero large emphasis −75
--tracking-display -0.05em Main title −50
--tracking-heading -0.05em H1–H4 −50
--tracking-body -0.025em Body −25
--tracking-body-tight -0.05em Dense body −50
--tracking-caption 0 Caption/label 0

Pattern (values adjusted per brand): the larger the text, the stronger the negative letter-spacing — Korean large typography looks overly spaced between characters, so it is tightened, while captions and small text are left at 0. −25/−50/−75 The naming is an abbreviation of -0.025/-0.05/-0.075em.

L3 — semantic/shadcn role schema

  • --base-* Semantic roles: background/foreground/card/popover/surface/muted/primary/secondary/accent/destructive/border/input/ring/border-strong/chart-1..5/sidebar-*
  • Tailwind v4 @theme + @layer base/utilities
  • .dark Dark mode only via class → CSS variable switching ([HARD] dark: Direct use of Tailwind utilities prohibited)

--base-* → shadcn component role mapping table

--base-* 롤(쌍) shadcn consuming component Derivation source (L2)
background/foreground body/page root --color-bg/--color-ink
card/card-foreground Card·Dialog surface/ink
popover/popover-foreground Popover·Dropdown·Tooltip surface/ink
primary/primary-foreground Button(default)·Tabs active brand.primary/white
secondary/secondary-foreground Button(secondary)·Badge neutral-100/ink
muted/muted-foreground Skeleton/disabled/secondary text neutral-100/neutral-600
accent/accent-foreground hover background/Calendar accent/white
destructive/destructive-foreground Button(destructive)·Alert semantic.danger/white
border·input·ring Global border/Input border/focus ring neutral-200·neutral-200·primary
chart-1..5 Recharts/data visualization primary/warning/success/info/danger
sidebar-* Sidebar-only role set surface/ink/primary/muted mirror

@theme block template (Tailwind v4)

@import 'tailwindcss';

@theme {
  --font-sans: "<BrandFont>", system-ui, sans-serif;
  --font-mono: "<Mono>", ui-monospace, monospace;

  --color-primary-50:  #RRGGBB;   /* … 950까지 11단계 */
  --color-primary-600: #RRGGBB;   /* 라이트 주색 */
  --color-primary-500: #RRGGBB;   /* 다크 주색 */
  --color-success: #RRGGBB; --color-warning: #RRGGBB;
  --color-danger:  #RRGGBB; --color-info:    #RRGGBB;

  --radius-sm: 0.5rem; --radius-lg: 1rem; --radius-full: 9999px;
  --duration-fast: 150ms; --ease-out: cubic-bezier(0.4, 0, 0.2, 1);
}

:root { /* --base-* 라이트 롤 바인딩 */ }
.dark { /* --base-* 다크 오버라이드 */ }

.dark override rules

  • Dark mode is only .dark class → CSS variable redefinition. dark: Direct use of Tailwind utilities prohibited ([HARD]).
  • primary: brightness increased (ensuring contrast against dark backgrounds) — a bright value corresponding to the light hover tone.
  • border/input: opaque gray → translucent white rgba(255,255,255,0.06~0.16).
  • chart-1..5: overall brightness increased.
  • gradient: replaced with a brighter stop.
  • Only override targets are redeclared; the rest inherit from :root .

Conversion procedure (skeleton)

Forward — L1 → L2 → L3

  1. L1 load·validation — DTCG JSON parsing, $typeand other validity checks, group completeness check
  2. L1 → L2 derivation — flatten brand/neutral/semantic tokens into --color-*/--neutral-*/--tracking-* CSS variables, @font-face·Generate CDN font block
    • Rule: map the group category as the prefix token, and the last 1–2 segments as the suffix.
    • color.brand.<x>→--color-<x> (brand removed) · color.brand.background→--color-bg (alias)
    • color.neutral.<n>→--neutral-<n> · color.semantic.<x>→--color-<x> · color.gradient.<x>→--gradient-<x>
    • typography.family.<x>→--font-<x> · weight.<x>→--fw-<x> · size.<x>→--text-<x> · lineHeight.<x>→--lh-<x> · letterSpacing.<x>→--tracking-<x>
    • spacing.<n>→--space-<n> · radius.<x>→--radius-<x> · shadow.<x>→--shadow-<x> · motion.duration.<x>→--duration-<x> · motion.easing.<x>→--easing-<x>
    • Aliases (bg·fw·tracking·space·lh) are only those registered in the table — no inference.
  3. L2 → L3 derivation — bind primitive variables to --base-* semantic roles, @theme registration, .dark generate override set
    • brand.primary → primary·ring·sidebar-primary·sidebar-ring·chart-1
    • brand.ink → foreground·card-foreground·popover-foreground · brand.background → background · brand.surface → card·popover·surface·sidebar
    • neutral.100 → muted·secondary·sidebar-accent · neutral.200 → border·input · neutral.600 → muted-foreground
    • semantic: success→success+chart-3 · warning→warning+accent+chart-2 · info→info+chart-4 · danger→destructive+error+chart-5
    • chart derivation: chart-1=primary · 2=warning|accent · 3=success · 4=info · 5=danger (reuse status colors as data series to ensure palette consistency)
    • sidebar derivation: mirror-copy surface/ink/primary/muted → so that the sidebar uses the same palette as the body but is kept as separate roles to allow independent toggling
  4. FROZEN rule verification — encode the prohibited values declared by the source (e.g., #000000 prohibited → ink replacement), single-signature gradients, letter-spacing rules, etc. as comments at each layer
    • Mechanism: encode the prohibition rules declared by the source system in each layer's native comment syntax and inject them into the output. The converter does not create prohibited values on its own — if there is no declaration in the source, there is no injection.
    • Injection location: L1 = token $description + file top-level $description / L2 = CSS header comment (All tokens FROZEN per <출처>) + fallback value inline comment (/* #000 대체 */, /* 변경 금지 */) / L3 = header comment + [HARD] inline ([HARD] 다크모드는 .dark 클래스로만).
    • Verification: only cross-check whether the prohibited values declared by the source (e.g., #000000) have leaked into the output.
    • worked example (a list declared by a specific brand — not a universal law): #000 prohibited → ink replacement · no creating arbitrary gradients · comply with letter-spacing rules · dark: utility prohibited. Other brands come with different prohibition lists.

Reverse — L3/L2 → L1

  1. Identify source layer — determine whether the input is CSS (L2) or globals.css (L3)
  2. Token extraction — from CSS variables and@theme declarations, reverse-extract values and meanings
  3. DTCG reconstruction — reassemble the extracted values into hierarchical groups, $descriptionis derived on a best-effort basis (state explicitly when the original source is unknown)
    • Reverse mapping (invert the forward table but resolve aliases): --color-primary→color.brand.primary · --color-bg→color.brand.background · --neutral-<n>→color.neutral.<n> · --color-success류→color.semantic.<x> · --gradient-<x>→color.gradient.<x>
    • --fw-<x>→typography.weight.<x> · --text-<x>→size.<x> · --tracking-<x>→letterSpacing.<x> · --space-<n>→spacing.<n> · --duration-<x>→motion.duration.<x>
    • --base-<role>(L3) is an L2→L3 binding and does not correspond directly to an L1 group → restore the source token by back-referencing the binding table (forward 3 steps).
    • $description derivation policy: ① promote inline comments adjacent to the declaration → ② extract the source (guideline/system name) from the file header and use it as a prefix → ③ if it is a standard token, use the category standard description → ④ if all fail, do not invent, state "출처 미상" explicitly.

Round-trip integrity check

  • After L1 → L2 → L3 → (reverse) → L1' round trip, compare L1 ≟ L1'
  • Color: equality after hex normalization, typography: preserve family/weight/size, letter-spacing: no value loss
  • When a mismatch is found, produce a diff report at the layer/token level Normalization rules (applied identically to both values before comparison)
Item Normalization 예
hex case unify to lowercase #FFF→#fff
3-digit shorthand expand to 6-digit #fff→#ffffff
rgb()/rgba() integer channel/alpha normalization, convert to hex if needed rgb(255,255,255)→#ffffff
oklch() compare after color space conversion (specify tolerance if lossless is impossible) oklch(…)↔hex
dimension unify units (rem preferred), em preserve letter-spacing form 4px↔0.25rem
font stack compare preserving order after normalizing whitespace and quotes
gradient normalize angle/stop/color (colors per the hex rules above)

diff report format

## 라운드트립 diff (L1 vs L1')
| 왕복 | 토큰 경로 | 원본 | 왕복 후 | 판정 |
|---|---|---|---|---|
| L1→L2→L3→L1' | color.brand.primary | #RRGGBB | #RRGGBB | PASS |
| L1→L2→L3→L1' | typography.letterSpacing.body | -0.025em | -0.025em | PASS |
| L1→L2→L3→L1' | color.gradient.signature | linear-gradient(…) | (스톱 순서 상이) | FAIL |
정합: N PASS / M 불일치

output format

## 토큰 변환 완료 (L{src} → L{dst})

### 변환 요약
- 입력 계층: L{n} ([파일])
- 출력 계층: L{m}
- 변환 토큰: 색 N · 타이포 N · spacing N · radius N · shadow N · motion N

### 생성 파일
- [출력 경로]

### 라운드트립 검사
- 왕복 정합: PASS / N개 불일치
- (불일치 시) 계층·토큰 단위 diff

### FROZEN 규칙 검증
- [위반 없음 / 위반 목록]

usage examples

Example 1 — DTCG → shadcn forward direction (L1 → L3)

입력: 02-tokens.json (L1 DTCG)
동작:
  1) color.brand.primary(#RRGGBB) → --color-primary → --base-primary/--base-ring/--base-chart-1
  2) @theme에 --color-primary-50..950 등록, :root에 --base-* 롤 바인딩
  3) .dark 오버라이드 세트 생성(primary 명도 상향, border 반투명 화이트)
출력: globals.css (@import tailwindcss + @theme + :root + .dark + @layer)
요약: 색 N · 자간 6 · radius 7 · shadow 6 / FROZEN 위반 0

Example 2 — globals.css → DTCG reverse extraction (L3 → L1)

입력: globals.css (L3)
동작:
  1) @theme·:root·.dark 변수 선언 스캔
  2) --base-* 롤은 바인딩표 역참조로 원천 토큰 복원, --color-*/--neutral-*는 직접 역매핑
  3) 인접 주석에서 $description 유도, 불명은 "출처 미상"
출력: 02-tokens.json' (재구성 DTCG)
요약: 복원 토큰 N · $description 유도 N / 출처 미상 M

Example 3 — round-trip check (L1 → L2 → L3 → L1')

입력: 02-tokens.json (L1)
동작: 정규화(hex 소문자·shorthand 확장·rgb→hex) 후 L1 ≟ L1'
출력: diff 리포트
결과: 12/13 PASS, 1 불일치(gradient 스톱 순서) → 계층·토큰 단위 표기

Caveats

Do

  • Always treat L1 (DTCG) as the SSOT — L2/L3 are derivatives; when editing manually, update L1 first and then re-derive
  • Dark mode only via .dark class CSS variable switching — Tailwind dark: utility direct use prohibited
  • Preserve letter-spacing values losslessly across all layers — the core of Korean typography quality

Don't

  • Do not modify only L2/L3 and leave L1 unattended — it will be overwritten on the next re-derivation
  • #000000 Do not let FROZEN prohibited values such as these pass into conversion output
  • When reverse-extracting, do not arbitrarily invent unclear $description— if it cannot be derived, state "source unknown"

related skills

Skill when to use
moai-designer:design-handoff-reader Prerequisite: extract 3-tier token sources from the bundle
moai-designer:design-system-prep Prerequisite: synthesize assets → DESIGN.md (token source)
moai-designer:design-system-library Auxiliary: use 75 brand tokens as conversion input
moai-designer:design-sync-upload Follow-up: upload converted tokens to Claude Design