원문: https://github.com/facebook/astryx/wiki/Theming-Infrastructure · 번역 기준: 2026-09-03
Astryx 테마 시스템은 하나의 원칙 위에 세워져 있습니다: theme config는 디자인 시스템을 코드로 명문화한 표현이다. defineTheme() 함수는 선언적 config를 받아 두 가지 서로 다른 경로에서 소비 가능한 산출물을 만들어 냅니다.
관련 문서는 다음을 참고하세요:
⚠️ 동기화를 유지하세요. Night Watch Component Auditor는 테마 컨벤션을 자동으로 enforcement합니다. 여기의 규칙을 업데이트할 때는 대응하는 auditor 체크도 업데이트하세요. auditor가 여기에 문서화되지 않은 공백을 발견하면 여기에 추가하세요.
defineTheme() input
┌───────────────────────────────┐
│ name: 'ocean' │
│ color: { accent: '#0077B6' } │
│ typography: { scale, body } │
│ motion: { fast, medium } │
│ tokens: { --color-*, ... } │
│ components: { button: {...} }│
│ fonts: [{ family, url }] │
│ icons: { ... } │
└──────────┬────────────────────┘
│
┌──────────▼────────────────────┐
│ Higher-Order Transforms │
│ │
│ color ───────► accent, bg, │
│ surface, border│
│ text tokens │
│ typography ──► text/heading │
│ size tokens │
│ motion ──────► duration │
│ min/max tokens │
│ │
│ Explicit tokens override │
│ generated values (highest │
│ precedence) │
└──────────┬────────────────────┘
│
┌──────────▼────────────────────┐
│ DefinedTheme │
│ │
│ name: string │
│ tokens: Record<string,string>│
│ components: ComponentStyles │
│ fonts: ThemeFontSource[] │
│ icons: IconRegistry │
│ css: string (generated) │
└──────────┬────────────────────┘
│
┌─────────────┴───────────────┐
│ │
┌──────────▼───────────┐ ┌───────────▼───────────┐
│ Runtime Path │ │ Built Path │
│ │ │ │
│ <Theme │ │ astryx theme build │
│ theme={ocean}> │ │ → ocean.css │
│ │ │ → ocean.js │
│ Injects <style> at │ │ → ocean.d.ts │
│ runtime with CSS │ │ │
│ custom properties │ │ Static CSS file with │
│ on :scope │ │ all tokens + @scope │
│ │ │ component overrides + │
│ Good for: dev, │ │ variant type defs │
│ dynamic themes, │ │ │
│ theme switching │ │ Good for: production,│
└──────────────────────┘ │ CDN delivery, SSR, │
│ zero runtime cost │
└───────────────────────┘
두 경로 모두 동일한 CSS custom property를 만들어 냅니다. 컴포넌트는 어느 경로가 token을 전달했는지 알지도, 신경 쓰지도 않습니다.
| 그룹 | Prefix | 예시 |
|---|---|---|
| Color | --color-* |
--color-accent, --color-text-primary, --color-border |
| Spacing | --spacing-* |
--spacing-1부터 --spacing-12까지 |
| Size | --size-* |
--size-element-sm, --size-element-md, --size-element-lg |
| Radius | --radius-* |
--radius-none, --radius-inner, --radius-element, --radius-container, --radius-page, --radius-full |
| Shadow | --shadow-* |
--shadow-low, --shadow-med, --shadow-high, --shadow-inset-* |
| Duration | --duration-* |
--duration-fast, --duration-medium, --duration-slow (각각 -min/-max 포함) |
| Ease | --ease-* |
--ease-standard |
| Typography | --font-*, --text-* |
--font-family-body, --font-size-base, --text-body-size, --text-body-leading |
모든 color token은 자동 다크 모드를 위해 light-dark()를 사용합니다:
'--color-accent': 'light-dark(#0064E0, #2694FE)'
"회색" 배경을 담당하는 token은 네 가지입니다. 어떤 것을 골라야 하는지는 원하는 색조가 아니라 그 엘리먼트가 무엇인지에 따라 결정됩니다:
| Token | 역할 | 사용처 |
|---|---|---|
--color-neutral |
자기완결적(self-contained) 엘리먼트를 위한 neutral fill | 버튼(secondary), 배지, token, kbd, 아바타 fallback, 페이지네이션 아이템, segmented control, 선택된 내비게이션 아이템, 채팅 버블, 썸네일 |
--color-background-muted |
내부에 콘텐츠를 담는 컨테이너의 배경 | 섹션, 카드, 코드 블록, 테이블 zebra stripe, 비활성 input fill, progress/slider fill track, 리스트 row |
--color-track |
본문 휘도(luminance)에 대비되어 읽히는 얇은 채널 affordance | Slider rail, Spinner track |
--color-overlay-hover |
반투명 hover/active 피드백 | ghost 버튼의 hover, 리스트/메뉴 아이템 하이라이트, 테이블 row hover. 항상 @media (hover: hover) 또는 keyboard-highlight state를 통해 적용됩니다. --color-overlay-pressed는 그 active-state 쌍둥이입니다 |
결정 트리:
--color-overlay-hover (active일 때는 --color-overlay-pressed)--color-track--color-background-muted