Design Token
PAI Design token이 어떤 층으로 나뉘고 각 레이어가 어디서 쓰이는지 소개해요.
이 문서는 Color, Spacing, Typography처럼 개별 기준을 설명하는 문서와 달리, PAI Design이 token을 어떤 층으로 나누고 그 층이 component까지 어떻게 이어지는지 설명해요. token 값 자체보다 구조와 사용 순서를 먼저 이해하고 싶을 때 읽으면 좋아요.
Overview
PAI Design은 raw value를 바로 흩뿌리기보다, 의미를 담은 token과 컴포넌트 기준을 거쳐 사용하는 구조예요.
| 구분 | 예시 | 어디서 주로 쓰나요 |
|---|---|---|
| Primitive scale / palette | color.brand500, font.size.md, radius.lg | scale 기준을 정의하거나 semantic token을 만들 때 사용해요. |
| Semantic token | color.bg.surface, color.accent.fill, size.surface.md, layer.blocking | recipe, component, 제품 UI에서 역할이 이미 정해진 값을 사용할 때 먼저 사용해요. |
| Composed token | textStyles.body.md, textStyles.title.lg | 반복되는 텍스트 조합을 바로 적용할 때 사용해요. |
| Component API | Button, Dialog, HelpBubble | 앱에서 실제 UI를 만들 때 가장 먼저 사용하는 public layer예요. |
색상처럼 의미 계층이 분명한 영역은 semantic token을 먼저 두고, spacing처럼 shared scale 하나로 운영하는 영역은 같은 scale을 반복해서 쓰는 방식을 선택해요. 중요한 것은 값보다 의미와 반복 기준을 먼저 맞추는 거예요.
Layer Flow
PAI Design은 크게 아래 흐름으로 연결돼요.
@pai-design/tokens가 primitive scale, semantic token, text style 같은 공통 기준을 제공해요.@pai-design/specs가 컴포넌트의anatomy,variant,state,accessibility와 기본 스타일 기준을 정하고 generated contract를 만들어요.@pai-design/recipes가 generateddefinition/recipecontract에mechanics와 계산을 더해 실제 class 조합과 CSS를 만들어요.@pai-design/react-headless가 스타일 없는 behavior primitive를 제공해요. focus, dismiss, keyboard interaction, anchored positioning 같은 상호작용 기준은 여기서 먼저 구현돼요.@pai-design/react가 headless primitive와 recipe accessor를 조합해 public React component로 노출해요.
앱에서는 가능하면 @pai-design/react를 먼저 사용하고, product-specific layout이나 시스템 바깥 UI를 만들 때만 @pai-design/tokens로 내려가요.
여기서 중요한 점은 public component family와 headless primitive가 꼭 같은 이름으로 1:1 대응하지 않는다는 거예요. 예를 들어 HelpBubble은 public component family지만 내부적으로는 Popover primitive를 사용하고, AlertDialog는 Dialog primitive를 바탕으로 더 좁은 의미와 기본값을 제공해요. 반대로 generic primitive가 있다고 해서 반드시 같은 이름의 public component 문서와 recipe가 함께 생기는 것은 아니에요.
기본 사용법
역할이 이미 정해진 스타일이라면 semantic token이나 composed token을 먼저 사용해요.
import { textStyles, tokenVars } from "@pai-design/tokens";
const dialogSurfaceStyle = {
...textStyles.body.md,
background: tokenVars["color.bg.surface"],
maxWidth: tokenVars["size.surface.md"],
padding: tokenVars["space.4"],
};이처럼 배경, overlay 폭, 기본 간격처럼 역할이 이미 정해진 값은 semantic token을 먼저 고르고, 텍스트는 textStyles처럼 이미 조합된 기준을 먼저 사용해요.
반대로 color.brand500처럼 scale 값을 직접 고르는 일은 semantic token을 만들거나 palette 자체를 설명해야 하는 경우에만 남겨두는 편이 좋아요.
Package Boundaries
각 패키지는 아래 역할을 나눠 가져요.
| 패키지 | 무엇을 소유하나요 | 언제 먼저 열어보면 좋나요 |
|---|---|---|
@pai-design/tokens | scale, palette, semantic token, text style 같은 공통 기준 | 값과 이름 체계가 맞는지 확인할 때 |
@pai-design/specs | 컴포넌트의 anatomy, part 계약, variant, state, 기본 스타일 기준, generated contract | 컴포넌트 기준을 정하거나 문서화할 때 |
@pai-design/recipes | generated contract에 mechanics를 더한 CSS와 class 조합 | 계산이나 안전한 style 조립 규칙을 조정할 때 |
@pai-design/react-headless | 스타일 없는 behavior primitive와 상호작용 기준 | focus, dismiss, keyboard, overlay 같은 상호작용을 구현하거나 재사용할 때 |
@pai-design/react | 앱이 바로 사용하는 React component | 제품 화면에 실제 UI를 붙일 때 |
이 경계를 나눠두면 raw value 수정, token 의미 수정, component visual 수정, public API 변경을 같은 레벨의 문제로 섞지 않고 다룰 수 있어요.
자주 헷갈리는 말
아키텍처를 읽을 때는 아래 3개를 구분해두면 이해가 빨라요.
anatomy는 컴포넌트를 이루는 구조적 part 이름 모음이에요. 디자인 시스템에서 널리 쓰는 표현이고, Chakra UI처럼anatomy를 직접 공개하는 시스템도 있어요.slot은 보통 사용자가 내용을 꽂거나 구현이 DOM 자리를 나누는 더 좁은 의미로 써요. PAI에서는anatomy전체를slot과 같은 뜻으로 쓰지 않아요.stylingTarget은anatomy중에서definition과recipe가 직접 스타일링할 수 있는 part를 가리켜요. contract에 필요하지만 직접 스타일링하지 않는 part도anatomy에는 남을 수 있어요.state는 사용자가 느끼거나runtime이 드러내는 상태예요.stylingHooks는 public 상태보다 스타일 분기에 필요한 조건에 가까워요.
새 token을 추가할 때 기준
- 여러 component에서 같은 의미로 반복되는 값이라면 semantic token을 먼저 검토해요.
- scale 하나를 더 추가하면 충분한 상황이라면 semantic 이름을 새로 만들기보다 primitive scale을 확장해요.
- 특정 component에서만 쓰는 조정이라면 global token보다 recipe나 spec 쪽에서 해결할 수 있는지 먼저 봐요.
- 앱 한 곳에서만 쓰는 product-specific 표현이라면 시스템 token을 늘리기보다 app layer에서 지역적으로 다루는 편이 나아요.
새 이름을 추가하는 것보다, 이미 있는 의미를 반복해서 쓰는 쪽이 시스템을 더 안정적으로 유지해줘요.
다음에 읽으면 좋은 문서
- 실제 색상 역할과 palette는 Color에서 확인해요.
- spacing과 size를 언제 나눠 쓰는지는 Spacing에서 확인해요.
- text style과 atomic typography token은 Typography에서 확인해요.
- 앱에 theme와 CSS를 연결하는 방법은 Styling Integration에서 이어서 볼 수 있어요.