<!--
generated_at: 2026-07-14T07:06:47.151Z
schema_version: 1.0
source_versions:
  @vortex/ui-foundation: 1.67.0
  @vortex/ui-cals: 1.25.0
  @vortex/ui-icignal: 1.66.0
  @vortex/design-md: 0.1.0
-->

# Vortex Design System

> An enterprise design system built on a neutral Foundation scale, extended by
> two brand overlays — Cals (booking platform) and iCignal (analytics platform).
> This document is the AI-readable style fingerprint; every rule here applies to
> all three products unless overridden in a Brand Overlay block.

## 1. Visual Theme & Atmosphere

Vortex's aesthetic is what a reserved Korean operations center feels like — quiet, high-density,
utilitarian in the best sense. It's not built to impress; it's built to stay out of the way when
a user is managing 30 bookings across 5 regions, or scanning an analytics table with 200 rows and
12 columns. Where consumer design systems go for cinematic drama and generous whitespace, Vortex
goes for institutional calm — the visual equivalent of a well-organized binder rather than a
glossy magazine. Foundation is near-monochrome by choice; accent color appears only where it
carries meaning (interaction, status, destructive intent), and every millimeter of horizontal
space is assumed to be potentially load-bearing.

Vortex is an enterprise design system built for three distinct product surfaces that share a
single visual vocabulary. Foundation provides a neutral, chromatically quiet baseline — a ten-step
neutral gray scale paired with role-based semantic tokens (primary, secondary, tertiary, muted,
accent, destructive) that map meaning to color without committing to a specific brand. On top of
that baseline, two Brand Overlays extend the system: Cals replaces the role tokens with a
booking-platform palette (pink, blue, purple, plus five booking-status colors), while iCignal
overlays an analytics-platform palette (blue, green, orange, plus semantic status colors). The
overlay pattern means a UI built against Foundation roles renders coherently in any of the three
products with zero component code changes.

The system is explicitly Korean-first. Noto Sans KR is the default sans-serif and IBM Plex Mono
the default mono, reflecting a user base where Korean is a primary reading language and where
character density and horizontal rhythm differ from Latin scripts. Body text runs at 14px with
relaxed leading; headings compress to tight tracking for scannable dashboard interfaces. Typography
tokens are expressed as CSS custom properties (`--typography-h1-*`, `--typography-p-*`) that
consume a shared type scale, so layouts can reference roles rather than raw sizes.

Visual atmosphere leans reductive — neutral surfaces, sparing use of color, soft low-opacity
shadows, and radii clustered around a 10px base. Accent colors appear only on interactive or
status-bearing elements: primary CTAs, destructive actions, focus rings, chart series, and the
five status roles (info, success, warning, error). Long horizontal dashboards, data tables, and
form-dense screens are the native context — the system is designed to stay out of the way when
density is high, and step forward only when an interaction needs attention.

If you had to pick one guiding instinct: **tables outnumber hero images, density outranks
whitespace, and role tokens outrank brand personality.** Vortex is Korean business software
first, marketing surface second. When in doubt, collapse color toward neutral, compress padding
toward the 8px grid, and let the information itself carry the visual weight.

**Key characteristics:**

- Neutral Foundation gray scale (50–950) with semantic role tokens mapped on top.
- Two Brand Overlays (Cals, iCignal) that override role tokens without touching primitive scales.
- Korean-first typography: Noto Sans KR body, IBM Plex Mono code, 14px base.
- Ten-step radius scale (`--radius-none` through `--radius-4xl`, base `0.625rem`) plus pill (`999px`).
- Six-step shadow scale (`xs` to `2xl`) using low-opacity black (`#0000001a` / `#00000040`).
- Five dedicated status roles (info/success/warning/error/neutral) with foreground + border triplets.
- Interactive states (`hover`, `active`, `disabled`) are first-class CSS custom properties on every
  component, not afterthoughts.
- Five-series chart palette (`--chart-1` blue through `--chart-5` rose) reserved for data viz.
- Bias toward borders and neutral surface alternation over shadows for structural separation.
- Accent color is a budget: one primary, one destructive, five status, five chart series —
  everything else is neutral.

### Brand Voice

Per-brand voice authored by designers in `src/prose/overlays/{id}/concept.md`.
Use these keywords and tone axes when composing UI for a specific brand overlay.
Service-level presets (`layerType: service`) are listed separately in
[vortex-presets.md](./vortex-presets.md).

**Cals** — 청명한 스틸 블루 (Clear Steel Blue) · 딥 틸의 전문적 신뢰감 (Deep Teal Professionalism) · 밝고 정돈된 저작 환경 (Bright Authoring Canvas) · 단계적으로 안내하는 흐름 (Guided Step Flow) · 진입 장벽을 낮추는 차분함 (Calm Accessibility)
- 딥 틸 베이스 — 사이드바·헤더에 `primary-800` `#055A78`으로 엔터프라이즈 SaaS 신뢰감 형성 · `primary-500` Steel Blue `#9FCAD9` — CTA·Wizard 단계 진행·활성 요소에 집중 사용 · 밝은 저작 환경 `primary-50` `#F5FAFC` — 비개발자 사용자의 시각적 피로도 최소화 · 정돈된 Drag & Drop 캔버스 — 컴포넌트 패널·속성 패널·미리보기 구조 · Light / Dark Dual Mode — 개발 환경별 선택 가능
- domains: 로우코드 · SaaS · 디지털 전환 · 시민 개발자 · 엔터프라이즈 플랫폼
- match for: 시민 개발자·비전공자 대상 로우코드 SaaS 저작 플랫폼

**iCignal** — 고밀도 데이터의 집중 (High-Density Data Focus) · 차가운 분석적 블루 (Cold Analytical Blue) · 어두운 크롬과 밝은 콘텐츠의 선명한 대비 (Dark Chrome Contrast) · 절제된 워크플로우 (Restrained Workflow Precision) · 전문적 CRM 환경 (Professional CRM Atmosphere)
- 모던 75% · 미니멀 65% — 고밀도 데이터 화면 · 차가움 80% — Blue 팔레트 · 동적 65% — 실시간 데이터·workflow
- domains: CRM · 고객관계관리 · 엔터프라이즈 · B2B SaaS
- match for: CRM 특화 플랫폼 — 고객 관리·분석·워크플로우 자동화

### Service Presets

Brand overlay 위에 primary 토큰만 override하는 경량 변형입니다. 아래는 인덱스이며,
각 preset의 전체 overview·concept는 [vortex-presets.md](./vortex-presets.md)를 참조합니다.
사용자 요청에서 적합한 preset을 자동 선정하는 알고리즘은
[Section 12 — Service Preset Auto-Selection](#12-service-preset-auto-selection)을 참조합니다.

| id | base brand | primary | domains | match for | not for |
| --- | --- | --- | --- | --- | --- |
| `icignal-amber` | icignal | `#FEE500` | 스포츠 · 레저 · 골프 · 피트니스 · 라이프스타일 | 스포츠·레저·라이프스타일 플랫폼 — 활기차고 친근한 브랜드 무드가 필요한 소비자 서비스 | 엔터프라이즈 어드민 · B2B SaaS · 데이터 분석 · 항공 운항 · 금융 |
| `icignal-cyan` | icignal | `#9FCAD9` | 로우코드 · SaaS · 디지털 전환 · 시민 개발자 · 엔터프라이즈 저작 환경 | iCignal 기반 로우코드 저작 플랫폼 — 시민 개발자·비전공자 대상 | 소비자 서비스 · 스포츠/레저 · 데이터 분석 전문 CRM · 감성 브랜드 |
| `icignal-green` | icignal | `#5AC10C` | 친환경 · 그린테크 · ESG · 모빌리티 · 에너지 · 지속가능성 | 친환경·그린테크·모빌리티 도메인 B2B 서비스 | 소비자 예약 서비스 · 항공 운항 어드민 · 기업 백오피스 · AI 크리에이티브 |
| `icignal-orange` | icignal | `#ff8904` | 엔터프라이즈 어드민 · 백오피스 · B2B SaaS · 회원관리 · 운영 대시보드 | 대량 데이터 조회·관리 중심 기업용 백오피스/어드민 | 소비자 서비스 · 감성 브랜드 · 스포츠/레저 · 창의적 플랫폼 |
| `icignal-sky` | icignal | `#0183BF` | 항공 · 운항 관리 · 항공사 · 승무원 배치 · 정비 · 미션 크리티컬 | 항공·운항 관리 어드민 — 미션 크리티컬 데이터 통합 관리 | 소비자 서비스 · 스포츠/레저 · 로우코드 저작 · 창의적 플랫폼 |
| `icignal-violet` | icignal | `#8B72F3` | AI · 인공지능 · 크리에이티브 플랫폼 · 테크 스타트업 · 혁신 기술 | AI·크리에이티브·혁신 기술 도메인 플랫폼 | 기업 백오피스 어드민 · 데이터 집약 B2B · 항공 운항 · 전통 기업 서비스 |

## 2. Color Palette & Roles

Vortex's color system is built in three layers: a neutral Foundation scale, a palette
of semantic role tokens that map meaning to color, and two brand overlays (Cals, iCignal)
that override the role tokens without touching the scale. Every `var()` reference below
resolves through the Foundation `:root` block into a Tailwind v4 primitive.

### Semantic Roles (Foundation)

| Token | Value → Resolved |
| --- | --- |
| `--background` | `var(--color-white)` → `#ffffff` |
| `--foreground` | `var(--color-neutral-950)` → `#0a0a0a` |
| `--card` | `var(--background)` → `#ffffff` |
| `--card-foreground` | `var(--foreground)` → `#0a0a0a` |
| `--popover` | `var(--background)` → `#ffffff` |
| `--popover-foreground` | `var(--foreground)` → `#0a0a0a` |
| `--primary` | `var(--color-primary-900)` → `#171717` |
| `--primary-foreground` | `var(--color-primary-50)` → `#fafafa` |
| `--secondary` | `var(--color-neutral-100)` → `#f5f5f5` |
| `--secondary-foreground` | `var(--color-neutral-900)` → `#171717` |
| `--tertiary` | `var(--color-blue-300)` → `#93c5fd` |
| `--tertiary-foreground` | `var(--foreground)` → `#0a0a0a` |
| `--muted` | `var(--color-neutral-100)` → `#f5f5f5` |
| `--muted-foreground` | `var(--color-neutral-500)` → `#737373` |
| `--accent` | `var(--color-neutral-100)` → `#f5f5f5` |
| `--accent-foreground` | `var(--color-neutral-900)` → `#171717` |
| `--destructive` | `var(--color-red-500)` → `#ef4444` |
| `--destructive-foreground` | `var(--color-neutral-50)` → `#fafafa` |
| `--border` | `var(--color-neutral-200)` → `#e5e5e5` |
| `--input` | `var(--color-neutral-200)` → `#e5e5e5` |
| `--ring` | `var(--color-neutral-400)` → `#a3a3a3` |
| `--chart-1` | `var(--color-blue-500)` → `#3b82f6` |
| `--chart-2` | `var(--color-emerald-500)` → `#10b981` |
| `--chart-3` | `var(--color-purple-800)` → `#6b21a8` |
| `--chart-4` | `var(--color-amber-500)` → `#f59e0b` |
| `--chart-5` | `var(--color-rose-500)` → `#f43f5e` |
| `--sidebar` | `var(--color-neutral-50)` → `#fafafa` |
| `--sidebar-foreground` | `var(--color-neutral-950)` → `#0a0a0a` |
| `--sidebar-primary` | `var(--primary)` → `#171717` |
| `--header-background` | `var(--color-neutral-50)` → `#fafafa` |
| `--header-foreground` | `var(--color-neutral-950)` → `#0a0a0a` |
| `--status-info` | `var(--color-slate-100)` → `#f1f5f9` |
| `--status-success` | `var(--color-green-100)` → `#dcfce7` |
| `--status-warning` | `var(--color-amber-100)` → `#fef3c7` |
| `--status-error` | `var(--color-red-100)` → `#fee2e2` |

### Primary Scale & Shadow Anchors

| Token | Value → Resolved |
| --- | --- |
| `--color-primary-50` | `var(--color-neutral-50)` → `#fafafa` |
| `--color-primary-100` | `var(--color-neutral-100)` → `#f5f5f5` |
| `--color-primary-200` | `var(--color-neutral-200)` → `#e5e5e5` |
| `--color-primary-300` | `var(--color-neutral-300)` → `#d4d4d4` |
| `--color-primary-400` | `var(--color-neutral-400)` → `#a3a3a3` |
| `--color-primary-500` | `var(--color-neutral-500)` → `#737373` |
| `--color-primary-600` | `var(--color-neutral-600)` → `#525252` |
| `--color-primary-700` | `var(--color-neutral-700)` → `#404040` |
| `--color-primary-800` | `var(--color-neutral-800)` → `#262626` |
| `--color-primary-900` | `var(--color-neutral-900)` → `#171717` |
| `--color-primary-950` | `var(--color-neutral-950)` → `#0a0a0a` |
| `--color-shadow-2xs` | `#0000001d` → `#0000001d` |
| `--color-shadow-xs` | `#0000001d` → `#0000001d` |
| `--color-shadow-sm` | `#0000001a` → `#0000001a` |
| `--color-shadow-md` | `#0000001a` → `#0000001a` |
| `--color-shadow-lg` | `#0000001a` → `#0000001a` |
| `--color-shadow-xl` | `#0000001a` → `#0000001a` |
| `--color-shadow-2xl` | `#00000040` → `#00000040` |

### Brand Overlays

#### Cals

| 항목            | 상세                                    |
| --------------- | --------------------------------------- |
| **브랜드 컬러** | Steel Blue `#9FCAD9` (Primary-500)      |
| **딥 틸**       | `#055A78` (Primary-800)                 |
| **라이트 배경** | `#F5FAFC` (Primary-50)                  |
| **모드**        | Light / Dark Dual                       |
| **폰트**        | Pretendard, Noto Sans KR, IBM Plex Mono |

No color token overrides detected.

#### iCignal

| 항목              | 상세                                    |
| ----------------- | --------------------------------------- |
| **브랜드 컬러**   | iCignal Blue `#2196f3` (Primary)        |
| **다크 사이드바** | Gray-800 dark chrome                    |
| **모드**          | Light / Dark Dual                       |
| **폰트**          | Pretendard, Noto Sans KR, IBM Plex Mono |

| Token | Overlay Value → Resolved |
| --- | --- |
| `--color-primary-50` | `var(--color-blue-50)` → `#eff6ff` |
| `--color-primary-100` | `var(--color-blue-100)` → `#dbeafe` |
| `--color-primary-200` | `var(--color-blue-200)` → `#bfdbfe` |
| `--color-primary-300` | `var(--color-blue-300)` → `#93c5fd` |
| `--color-primary-400` | `var(--color-blue-400)` → `#60a5fa` |
| `--color-primary-500` | `var(--color-blue-500)` → `#3b82f6` |
| `--color-primary-600` | `var(--color-blue-600)` → `#2563eb` |
| `--color-primary-700` | `var(--color-blue-700)` → `#1d4ed8` |
| `--color-primary-800` | `var(--color-blue-800)` → `#1e40af` |
| `--color-primary-900` | `var(--color-blue-900)` → `#1e3a8a` |
| `--color-primary-950` | `var(--color-blue-950)` → `#172554` |
| `--foreground` | `var(--color-zinc-200)` → `#e4e4e7` |
| `--card` | `var(--color-black)` → `#000000` |
| `--primary` | `var(--color-primary-500)` → `#737373` |
| `--primary-foreground` | `var(--background)` → `#ffffff` |
| `--secondary` | `var(--color-zinc-950)` → `#09090b` |
| `--secondary-foreground` | `var(--color-neutral-300)` → `#d4d4d4` |
| `--tertiary` | `var(--color-amber-500)` → `#f59e0b` |
| `--tertiary-foreground` | `var(--background)` → `#ffffff` |
| `--muted` | `var(--color-zinc-900)` → `#18181b` |
| `--muted-foreground` | `var(--color-neutral-600)` → `#525252` |
| `--accent` | `var(--color-zinc-900)` → `#18181b` |
| `--accent-foreground` | `var(--foreground)` → `#0a0a0a` |
| `--destructive-foreground` | `var(--color-black)` → `#000000` |
| `--border` | `var(--color-zinc-800)` → `#27272a` |
| `--input` | `var(--color-zinc-700)` → `#3f3f46` |
| `--ring` | `var(--color-zinc-800)` → `#27272a` |
| `--sidebar` | `var(--color-zinc-900)` → `#18181b` |
| `--sidebar-foreground` | `var(--color-zinc-200)` → `#e4e4e7` |
| `--header-background` | `var(--sidebar)` → `#fafafa` |
| `--header-foreground` | `var(--color-white)` → `#ffffff` |

## 3. Typography Rules

### Font Family

- **Display**: `Noto Sans KR`
- **Body**: `Noto Sans KR`
- **Mono**: `IBM Plex Mono`

### Hierarchy

| Role | Size | Weight | Line Height | Letter Spacing | Notes |
| --- | --- | --- | --- | --- | --- |
| h1 | `var(--text-4xl)` → `2.25rem` | `var(--font-weight-bold)` → `700` | `var(--leading-tight)` → `1.25` | `var(--tracking-tight)` → `-0.025em` | Page title — one per screen, defines the main subject. |
| h2 | `var(--text-3xl)` → `1.875rem` | `var(--font-weight-semibold)` → `600` | `var(--leading-tight)` → `1.25` | `var(--tracking-tight)` → `-0.025em` | Major section heading — structural divider within a page. |
| h3 | `var(--text-2xl)` → `1.5rem` | `var(--font-weight-semibold)` → `600` | `var(--leading-tight)` → `1.25` | `var(--tracking-tight)` → `-0.025em` | Sub-section heading — nested under h2. |
| h4 | `var(--text-xl)` → `1.25rem` | `var(--font-weight-semibold)` → `600` | `var(--leading-tight)` → `1.25` | `var(--tracking-tight)` → `-0.025em` | Minor heading — card titles, grouping labels. |
| lead | `var(--text-base)` → `1rem` | `-` | `var(--leading-normal)` → `1.5` | `var(--tracking-normal)` → `0em` | Intro paragraph directly after h1/h2, larger than body. |
| large | `var(--text-lg)` → `1.125rem` | `var(--font-weight-semibold)` → `600` | `var(--leading-normal)` → `1.5` | `var(--tracking-normal)` → `0em` | Emphasized body text for callouts or hero supporting copy. |
| p | `-` | `-` | `var(--leading-normal)` → `1.5` | `var(--tracking-normal)` → `0em` | Body paragraph — the default reading text. |
| small | `var(--text-xs)` → `0.75rem` | `var(--font-weight-normal)` → `400` | `var(--leading-normal)` → `1.5` | `var(--tracking-normal)` → `0em` | Caption, timestamp, metadata beneath primary content. |
| muted | `var(--text-sm)` → `0.875rem` | `-` | `var(--leading-normal)` → `1.5` | `var(--tracking-normal)` → `0em` | Helper text, secondary descriptions, hint copy. |
| blockquote | `-` | `-` | `var(--leading-relaxed)` → `1.625` | `var(--tracking-normal)` → `0em` | Quoted passage or callout block with left border. |
| inline-code | `var(--text-sm)` → `0.875rem` | `var(--font-weight-semibold)` → `600` | `var(--leading-normal)` → `1.5` | `var(--tracking-normal)` → `0em` | Inline code or keyboard token within prose. |

_OpenType features are not set globally in Foundation — apps may opt in per-surface
via `font-feature-settings` in app-level CSS (see Cals's `rlig 1, calt 1` pattern)._

### Scale

| Step | Token |
| --- | --- |
| xs | `var(--text-xs)` |
| sm | `var(--text-sm)` |
| base | `var(--text-base)` |
| lg | `var(--text-lg)` |
| xl | `var(--text-xl)` |
| 2xl | `var(--text-2xl)` |
| 3xl | `var(--text-3xl)` |
| 4xl | `var(--text-4xl)` |

## 4. Component Stylings

Vortex components follow a consistent CVA (class-variance-authority) architecture: each component
declares a base class list plus two variant dimensions — typically `variant` (semantic role) and
`size` (5–10 step scale from `xs` to `xl`, with `icon-*` parallel sizes where applicable). All
colors and sizing values reference CSS custom properties with a component prefix (e.g.
`var(--button-background-default-hover)`), so the same component code renders in Foundation, Cals,
and iCignal styles by swapping token values.

This section lists the six standard categories that cover the majority of UI construction. For the
complete per-component reference — every variant key, class list, CSS token, and default value,
plus every Brand Overlay addition and override — see
[vortex-components.md](./vortex-components.md).

**Categorization rules:**

- **Buttons** — action triggers. Includes `Button` and `ButtonGroup`. Seven `variant` values
  (default / destructive / secondary / outline / ghost / link / tertiary) × five `size` values
  (xs / sm / md / lg / xl) plus five parallel icon sizes.
- **Cards & Containers** — bounded surfaces that group related content. Includes `Card`,
  `CardHeader`, `CardContent`, `CardFooter`, plus `Sheet`, `Drawer`, and `Dialog` for overlayed
  surfaces.
- **Inputs & Forms** — data-entry primitives. `Input`, `Textarea`, `Select`, `Checkbox`,
  `RadioGroup`, `Switch`, `DatePicker`, `Combobox`, `InputNumber`, `InputOtp`.
- **Badges, Tags & Pills** — compact status indicators. `Badge` (seven semantic variants matching
  the status scale).
- **Navigation** — structural wayfinding. `Tabs`, `Breadcrumb`, `NavigationMenu`, `Pagination`,
  `Sidebar`.
- **Distinctive Components** — high-signal surfaces that define the Vortex aesthetic more than
  the sum of primitives. `DataTable` (virtualized, column-configurable, iCignal overlay),
  `FullCalendar` (Cals booking surface), `WorkflowDesigner` (iCignal), `Chart` (recharts-based,
  five-series palette).

### Buttons

**Representative component** — use the scheme below as the template for every other
component in this category.

#### Button

Source: `packages/ui-foundation/src/components/ui/button/button.variants.ts`

Default: `{"variant":"default","size":"md"}`

**`variant` dimension**

- **`default`** _(default)_
  - _Use:_ Primary CTA — Save, Continue, Submit (one per section).
  - base: `bg-[var(--button-background-default)]` · `text-[var(--button-foreground-default)]` · `border-[var(--button-border-default)]` · `shadow-[var(--button-shadow-default)]`
  - hover: `bg-[var(--button-background-default-hover)]` · `text-[var(--button-foreground-default-hover)]` · `border-[var(--button-border-default-hover)]`
  - active: `bg-[var(--button-background-default-active)]` · `text-[var(--button-foreground-default-active)]` · `border-[var(--button-border-default-active)]`
  - disabled: `bg-[var(--button-background-default-disabled)]` · `text-[var(--button-foreground-default-disabled)]` · `border-[var(--button-border-default-disabled)]`
  - resolved tokens (13 total):
    - `var(--button-background-default)` → `#171717`
    - `var(--button-foreground-default)` → `#fafafa`
    - `var(--button-border-default)` → `#171717`
    - `var(--button-shadow-default)`
    - `var(--button-background-default-hover)` → `#171717`
    - `var(--button-foreground-default-hover)` → `#fafafa`
    - _+ 7 more_
- **`destructive`**
  - _Use:_ Destructive actions — Delete, Remove, Cancel Booking.
  - base: `bg-[var(--button-background-destructive)]` · `text-[var(--button-foreground-destructive)]` · `border-[var(--button-border-destructive)]` · `shadow-[var(--button-shadow-destructive)]`
  - hover: `bg-[var(--button-background-destructive-hover)]` · `text-[var(--button-foreground-destructive-hover)]` · `border-[var(--button-border-destructive-hover)]`
  - active: `bg-[var(--button-background-destructive-active)]` · `text-[var(--button-foreground-destructive-active)]` · `border-[var(--button-border-destructive-active)]`
  - disabled: `bg-[var(--button-background-destructive-disabled)]` · `text-[var(--button-foreground-destructive-disabled)]` · `border-[var(--button-border-destructive-disabled)]`
  - resolved tokens (13 total):
    - `var(--button-background-destructive)`
    - `var(--button-foreground-destructive)` → `#ef4444`
    - `var(--button-border-destructive)`
    - `var(--button-shadow-destructive)`
    - `var(--button-background-destructive-hover)`
    - `var(--button-foreground-destructive-hover)` → `#ef4444`
    - _+ 7 more_
- **`secondary`**
  - _Use:_ Secondary action alongside a primary CTA (Save + Discard, Confirm + Back).
  - base: `bg-[var(--button-background-secondary)]` · `text-[var(--button-foreground-secondary)]` · `border-[var(--button-border-secondary)]` · `shadow-[var(--button-shadow-secondary)]`
  - hover: `bg-[var(--button-background-secondary-hover)]` · `text-[var(--button-foreground-secondary-hover)]` · `border-[var(--button-border-secondary-hover)]`
  - active: `bg-[var(--button-background-secondary-active)]` · `text-[var(--button-foreground-secondary-active)]` · `border-[var(--button-border-secondary-active)]`
  - disabled: `bg-[var(--button-background-secondary-disabled)]` · `text-[var(--button-foreground-secondary-disabled)]` · `border-[var(--button-border-secondary-disabled)]`
  - resolved tokens (13 total):
    - `var(--button-background-secondary)` → `#f5f5f5`
    - `var(--button-foreground-secondary)` → `#171717`
    - `var(--button-border-secondary)` → `#f5f5f5`
    - `var(--button-shadow-secondary)`
    - `var(--button-background-secondary-hover)` → `#f5f5f5`
    - `var(--button-foreground-secondary-hover)` → `#171717`
    - _+ 7 more_
- **`outline`**
  - _Use:_ Lower-emphasis action with a visible affordance — toolbar buttons, filter toggles.
  - base: `bg-[var(--button-background-outline)]` · `text-[var(--button-foreground-outline)]` · `border-[var(--button-border-outline)]` · `shadow-[var(--button-shadow-outline)]`
  - hover: `bg-[var(--button-background-outline-hover)]` · `text-[var(--button-foreground-outline-hover)]` · `border-[var(--button-border-outline-hover)]`
  - active: `bg-[var(--button-background-outline-active)]` · `text-[var(--button-foreground-outline-active)]` · `border-[var(--button-border-outline-active)]`
  - disabled: `bg-[var(--button-background-outline-disabled)]` · `text-[var(--button-foreground-outline-disabled)]` · `border-[var(--button-border-outline-disabled)]`
  - resolved tokens (13 total):
    - `var(--button-background-outline)` → `#ffffff`
    - `var(--button-foreground-outline)` → `#0a0a0a`
    - `var(--button-border-outline)` → `#e5e5e5`
    - `var(--button-shadow-outline)`
    - `var(--button-background-outline-hover)` → `#f5f5f5`
    - `var(--button-foreground-outline-hover)` → `#0a0a0a`
    - _+ 7 more_
- **`ghost`**
  - _Use:_ Compact inline action without visual weight — table row actions, icon triggers in dense UI.
  - base: `bg-[var(--button-background-ghost)]` · `text-[var(--button-foreground-ghost)]` · `border-[var(--button-border-ghost)]` · `shadow-[var(--button-shadow-ghost)]`
  - hover: `bg-[var(--button-background-ghost-hover)]` · `text-[var(--button-foreground-ghost-hover)]` · `border-[var(--button-border-ghost-hover)]`
  - active: `bg-[var(--button-background-ghost-active)]` · `text-[var(--button-foreground-ghost-active)]` · `border-[var(--button-border-ghost-active)]`
  - disabled: `bg-[var(--button-background-ghost-disabled)]` · `text-[var(--button-foreground-ghost-disabled)]` · `border-[var(--button-border-ghost-disabled)]`
  - resolved tokens (13 total):
    - `var(--button-background-ghost)` → `transparent`
    - `var(--button-foreground-ghost)` → `#0a0a0a`
    - `var(--button-border-ghost)` → `transparent`
    - `var(--button-shadow-ghost)`
    - `var(--button-background-ghost-hover)` → `#f5f5f5`
    - `var(--button-foreground-ghost-hover)` → `#0a0a0a`
    - _+ 7 more_
- **`link`**
  - _Use:_ Text-styled CTA embedded in prose — 'Learn more', 'Read docs'.
  - base: `bg-[var(--button-background-link)]` · `text-[var(--button-foreground-link)]` · `border-[var(--button-border-link)]` · `shadow-[var(--button-shadow-link)]` · `select-text`
  - hover: `bg-[var(--button-background-link-hover)]` · `text-[var(--button-foreground-link-hover)]` · `border-[var(--button-border-link-hover)]`
  - active: `bg-[var(--button-background-link-active)]` · `text-[var(--button-foreground-link-active)]` · `border-[var(--button-border-link-active)]`
  - disabled: `bg-[var(--button-background-link-disabled)]` · `text-[var(--button-foreground-link-disabled)]` · `border-[var(--button-border-link-disabled)]`
  - resolved tokens (13 total):
    - `var(--button-background-link)` → `transparent`
    - `var(--button-foreground-link)` → `#171717`
    - `var(--button-border-link)` → `transparent`
    - `var(--button-shadow-link)`
    - `var(--button-background-link-hover)` → `transparent`
    - `var(--button-foreground-link-hover)` → `#171717`
    - _+ 7 more_
- **`tertiary`**
  - _Use:_ Tertiary emphasis, between secondary and ghost — disclosure, optional shortcuts.
  - base: `bg-[var(--button-background-tertiary)]` · `text-[var(--button-foreground-tertiary)]` · `border-[var(--button-border-tertiary)]` · `shadow-[var(--button-shadow-tertiary)]`
  - hover: `bg-[var(--button-background-tertiary-hover)]` · `text-[var(--button-foreground-tertiary-hover)]` · `border-[var(--button-border-tertiary-hover)]`
  - active: `bg-[var(--button-background-tertiary-active)]` · `text-[var(--button-foreground-tertiary-active)]` · `border-[var(--button-border-tertiary-active)]`
  - disabled: `bg-[var(--button-background-tertiary-disabled)]` · `text-[var(--button-foreground-tertiary-disabled)]` · `border-[var(--button-border-tertiary-disabled)]`
  - resolved tokens (13 total):
    - `var(--button-background-tertiary)` → `#93c5fd`
    - `var(--button-foreground-tertiary)` → `#0a0a0a`
    - `var(--button-border-tertiary)` → `#93c5fd`
    - `var(--button-shadow-tertiary)`
    - `var(--button-background-tertiary-hover)`
    - `var(--button-foreground-tertiary-hover)` → `#0a0a0a`
    - _+ 7 more_

**`size` dimension**

- **`md`** _(default)_
  - _Use:_ Default size for standalone CTAs in forms, dialogs, cards.
  - base: `h-[var(--button-height-md)]` · `rounded-[var(--button-radius-md)]` · `text-[length:var(--button-font-size-md)]` · `px-[var(--button-padding-x-md)]` · `gap-[var(--button-gap-md)]` · `in-data-[slot=button-group]:rounded-md` · `has-data-[icon=inline-start]:pl-2` · `has-data-[icon=inline-end]:pr-2`
  - resolved tokens (5 total):
    - `var(--button-height-md)` → `2.25rem`
    - `var(--button-radius-md)` → `0.3125rem`
    - `var(--button-font-size-md)` → `1rem`
    - `var(--button-padding-x-md)` → `1rem`
    - `var(--button-gap-md)` → `0.5rem`
- **`xs`**
  - _Use:_ Dense contexts — table row actions, compact toolbars.
  - base: `h-[var(--button-height-xs)]` · `rounded-[var(--button-radius-xs)]` · `text-[length:var(--button-font-size-xs)]` · `px-[var(--button-padding-x-xs)]` · `gap-[var(--button-gap-xs)]` · `in-data-[slot=button-group]:rounded-md` · `has-data-[icon=inline-start]:pl-1.5` · `has-data-[icon=inline-end]:pr-1.5`
  - resolved tokens (5 total):
    - `var(--button-height-xs)` → `1.5rem`
    - `var(--button-radius-xs)` → `0.2344rem`
    - `var(--button-font-size-xs)` → `0.75rem`
    - `var(--button-padding-x-xs)` → `0.5rem`
    - `var(--button-gap-xs)` → `0.25rem`
- **`sm`**
  - _Use:_ Forms with moderate density, secondary toolbars.
  - base: `h-[var(--button-height-sm)]` · `rounded-[var(--button-radius-sm)]` · `text-[length:var(--button-font-size-sm)]` · `px-[var(--button-padding-x-sm)]` · `gap-[var(--button-gap-sm)]` · `in-data-[slot=button-group]:rounded-md` · `has-data-[icon=inline-start]:pl-1.5` · `has-data-[icon=inline-end]:pr-1.5`
  - resolved tokens (5 total):
    - `var(--button-height-sm)` → `2rem`
    - `var(--button-radius-sm)` → `0.2344rem`
    - `var(--button-font-size-sm)` → `0.875rem`
    - `var(--button-padding-x-sm)` → `0.5rem`
    - `var(--button-gap-sm)` → `0.25rem`
- **`lg`**
  - _Use:_ Landing or hero sections where the CTA is the focal point.
  - base: `h-[var(--button-height-lg)]` · `rounded-[var(--button-radius-lg)]` · `text-[length:var(--button-font-size-lg)]` · `px-[var(--button-padding-x-lg)]` · `gap-[var(--button-gap-lg)]` · `has-data-[icon=inline-start]:pl-3` · `has-data-[icon=inline-end]:pr-3`
  - resolved tokens (5 total):
    - `var(--button-height-lg)` → `2.5rem`
    - `var(--button-radius-lg)` → `0.3125rem`
    - `var(--button-font-size-lg)` → `1.125rem`
    - `var(--button-padding-x-lg)` → `1rem`
    - `var(--button-gap-lg)` → `0.5rem`
- **`xl`**
  - _Use:_ Marketing or feature-launch moments, maximum presence.
  - base: `h-[var(--button-height-xl)]` · `rounded-[var(--button-radius-xl)]` · `text-[length:var(--button-font-size-xl)]` · `px-[var(--button-padding-x-xl)]` · `gap-[var(--button-gap-xl)]` · `has-data-[icon=inline-start]:pl-3` · `has-data-[icon=inline-end]:pr-3`
  - resolved tokens (5 total):
    - `var(--button-height-xl)` → `3rem`
    - `var(--button-radius-xl)` → `0.4688rem`
    - `var(--button-font-size-xl)` → `1.25rem`
    - `var(--button-padding-x-xl)` → `1.25rem`
    - `var(--button-gap-xl)` → `0.5rem`
- **`icon`**
  - _Use:_ Icon-only button at md height — bare icon with no text label.
  - base: `size-[var(--button-icon-md)]` · `rounded-[var(--button-radius-md)]`
  - resolved tokens (3 total):
    - `var(--button-icon-md)` → `2.25rem`
    - `var(--button-radius-md)` → `0.3125rem`
    - `var(--button-font-size-md)` → `1rem`
- **`icon-xs`**
  - _Use:_ Icon-only for dense table row controls.
  - base: `size-[var(--button-icon-xs)]` · `rounded-[var(--button-radius-xs)]` · `in-data-[slot=button-group]:rounded-md`
  - resolved tokens (3 total):
    - `var(--button-icon-xs)` → `1.5rem`
    - `var(--button-radius-xs)` → `0.2344rem`
    - `var(--button-font-size-xs)` → `0.75rem`
- **`icon-sm`**
  - _Use:_ Icon-only for compact toolbars and inline editors.
  - base: `size-[var(--button-icon-sm)]` · `rounded-[var(--button-radius-sm)]` · `in-data-[slot=button-group]:rounded-md`
  - resolved tokens (3 total):
    - `var(--button-icon-sm)` → `2rem`
    - `var(--button-radius-sm)` → `0.2344rem`
    - `var(--button-font-size-sm)` → `0.875rem`
- **`icon-lg`**
  - _Use:_ Icon-only for prominent standalone actions.
  - base: `size-[var(--button-icon-lg)]` · `rounded-[var(--button-radius-lg)]`
  - resolved tokens (3 total):
    - `var(--button-icon-lg)` → `2.5rem`
    - `var(--button-radius-lg)` → `0.3125rem`
    - `var(--button-font-size-lg)` → `1.125rem`
- **`icon-xl`**
  - _Use:_ Icon-only hero action, marketing surfaces.
  - base: `size-[var(--button-icon-xl)]` · `rounded-[var(--button-radius-xl)]`
  - resolved tokens (3 total):
    - `var(--button-icon-xl)` → `3rem`
    - `var(--button-radius-xl)` → `0.4688rem`
    - `var(--button-font-size-xl)` → `1.25rem`

**Also in this category:**

- **ActionButtons** — variants: none
- **ButtonGroup** — variants: size(5) · default: `{"size":"md"}`

### Cards & Containers

**Representative component** — use the scheme below as the template for every other
component in this category.

#### Card

Source: `packages/ui-foundation/src/components/ui/card/card.variants.ts`

Layout: gap: `var(--card-gap)` → `1rem`

_This component has no CVA variants — styling is structural only._

**Also in this category:**

- **AlertDialog** — variants: none
- **Dialog** — variants: none
- **Drawer** — variants: none
- **HoverCard** — variants: none
- **Popover** — variants: none
- **Sheet** — variants: none

### Inputs & Forms

**Representative component** — use the scheme below as the template for every other
component in this category.

#### Input

Source: `packages/ui-foundation/src/components/ui/input/input.variants.ts`

Layout: border: 1px solid

Default: `{"size":"md"}`

**`size` dimension**

- **`xs`**
  - _Use:_ Dense filter rows above data tables, toolbar search.
  - base: `h-[var(--input-height-xs)]` · `rounded-[var(--input-radius-xs)]` · `text-[length:var(--input-font-size-xs)]` · `md:text-[length:var(--input-font-size-xs)]` · `px-[var(--input-padding-x-xs)]`
  - resolved tokens (4 total):
    - `var(--input-height-xs)`
    - `var(--input-radius-xs)`
    - `var(--input-font-size-xs)`
    - `var(--input-padding-x-xs)`
- **`sm`**
  - _Use:_ Compact inline editors, secondary filter forms.
  - base: `h-[var(--input-height-sm)]` · `rounded-[var(--input-radius-sm)]` · `text-[length:var(--input-font-size-sm)]` · `md:text-[length:var(--input-font-size-sm)]` · `px-[var(--input-padding-x-sm)]`
  - resolved tokens (4 total):
    - `var(--input-height-sm)`
    - `var(--input-radius-sm)`
    - `var(--input-font-size-sm)`
    - `var(--input-padding-x-sm)`
- **`md`** _(default)_
  - _Use:_ Default size for form inputs and dialogs.
  - base: `h-[var(--input-height-md)]` · `rounded-[var(--input-radius-md)]` · `text-[length:var(--input-font-size-md)]` · `md:text-[length:var(--input-font-size-md)]` · `px-[var(--input-padding-x-md)]`
  - resolved tokens (4 total):
    - `var(--input-height-md)`
    - `var(--input-radius-md)`
    - `var(--input-font-size-md)`
    - `var(--input-padding-x-md)`
- **`lg`**
  - _Use:_ Spacious forms with strong visual hierarchy (single-step flows).
  - base: `h-[var(--input-height-lg)]` · `rounded-[var(--input-radius-lg)]` · `text-[length:var(--input-font-size-lg)]` · `md:text-[length:var(--input-font-size-lg)]` · `px-[var(--input-padding-x-lg)]`
  - resolved tokens (4 total):
    - `var(--input-height-lg)`
    - `var(--input-radius-lg)`
    - `var(--input-font-size-lg)`
    - `var(--input-padding-x-lg)`
- **`xl`**
  - _Use:_ Hero forms — landing signup, onboarding.
  - base: `h-[var(--input-height-xl)]` · `rounded-[var(--input-radius-xl)]` · `text-[length:var(--input-font-size-xl)]` · `md:text-[length:var(--input-font-size-xl)]` · `px-[var(--input-padding-x-xl)]`
  - resolved tokens (4 total):
    - `var(--input-height-xl)`
    - `var(--input-radius-xl)`
    - `var(--input-font-size-xl)`
    - `var(--input-padding-x-xl)`

**Also in this category:**

- **Checkbox** — variants: size(5) · default: `{"size":"md"}`
- **Combobox** — variants: size(5) · default: `{"size":"md"}`
- **DatePicker** — variants: size(5) · default: `{"size":"md"}`
- **Field** — variants: none
- **Form** — variants: none
- **InputNumber** — variants: size(5) · default: `{"size":"md"}`
- **InputOtp** — variants: size(5) · default: `{"size":"md"}`
- **Label** — variants: size(5) · default: `{"size":"md"}`
- **RadioGroup** — variants: size(5) · default: `{"size":"md"}`
- **Select** — variants: size(5) · default: `{"size":"md"}`
- **Switch** — variants: size(5) · default: `{"size":"md"}`
- **Textarea** — variants: resizable(2), size(5) · default: `{"size":"md"}`
- **CheckboxGroup** — variants: orientation(2) · default: `{"orientation":"horizontal"}`
- **InputPassword** — variants: size(5), disabled(1), readOnly(1) · default: `{"size":"md"}`

### Badges, Tags & Pills

**Representative component** — use the scheme below as the template for every other
component in this category.

#### Badge

Source: `packages/ui-foundation/src/components/ui/badge/badge.variants.ts`

Layout: display: inline-flex · align: CENTER · justify: CENTER · border: 1px solid

Default: `{"variant":"default","size":"md"}`

**`variant` dimension**

- **`default`** _(default)_
  - _Use:_ Neutral status tag (draft, unread, misc).
  - base: `bg-[var(--badge-background-default)]` · `dark:bg-[var(--badge-background-default)]` · `text-[var(--badge-foreground-default)]` · `dark:text-[var(--badge-foreground-default)]` · `border-[var(--badge-border-default)]` · `dark:border-[var(--badge-border-default)]` · `dark:hover:bg-[var(--badge-background-default-hover)]` · `dark:hover:text-[var(--badge-foreground-default-hover)]` · `dark:hover:border-[var(--badge-border-default-hover)]`
  - hover: `bg-[var(--badge-background-default-hover)]` · `text-[var(--badge-foreground-default-hover)]` · `border-[var(--badge-border-default-hover)]`
  - resolved tokens (6 total):
    - `var(--badge-background-default)` → `#171717`
    - `var(--badge-foreground-default)` → `#fafafa`
    - `var(--badge-border-default)` → `transparent`
    - `var(--badge-background-default-hover)` → `#171717`
    - `var(--badge-foreground-default-hover)` → `#fafafa`
    - `var(--badge-border-default-hover)` → `transparent`
- **`destructive`**
  - _Use:_ Error or blocking state indicator.
  - base: `bg-[var(--badge-background-destructive)]` · `dark:bg-[var(--badge-background-destructive)]` · `text-[var(--badge-foreground-destructive)]` · `dark:text-[var(--badge-foreground-destructive)]` · `border-[var(--badge-border-destructive)]` · `dark:border-[var(--badge-border-destructive)]` · `dark:hover:bg-[var(--badge-background-destructive-hover)]` · `dark:hover:text-[var(--badge-foreground-destructive-hover)]` · `dark:hover:border-[var(--badge-border-destructive-hover)]`
  - hover: `bg-[var(--badge-background-destructive-hover)]` · `text-[var(--badge-foreground-destructive-hover)]` · `border-[var(--badge-border-destructive-hover)]`
  - resolved tokens (6 total):
    - `var(--badge-background-destructive)`
    - `var(--badge-foreground-destructive)` → `#ef4444`
    - `var(--badge-border-destructive)` → `transparent`
    - `var(--badge-background-destructive-hover)`
    - `var(--badge-foreground-destructive-hover)` → `#ef4444`
    - `var(--badge-border-destructive-hover)` → `transparent`
- **`secondary`**
  - _Use:_ Secondary categorization without status weight.
  - base: `bg-[var(--badge-background-secondary)]` · `dark:bg-[var(--badge-background-secondary)]` · `text-[var(--badge-foreground-secondary)]` · `dark:text-[var(--badge-foreground-secondary)]` · `border-[var(--badge-border-secondary)]` · `dark:border-[var(--badge-border-secondary)]` · `dark:hover:bg-[var(--badge-background-secondary-hover)]` · `dark:hover:text-[var(--badge-foreground-secondary-hover)]` · `dark:hover:border-[var(--badge-border-secondary-hover)]`
  - hover: `bg-[var(--badge-background-secondary-hover)]` · `text-[var(--badge-foreground-secondary-hover)]` · `border-[var(--badge-border-secondary-hover)]`
  - resolved tokens (6 total):
    - `var(--badge-background-secondary)` → `#f5f5f5`
    - `var(--badge-foreground-secondary)` → `#171717`
    - `var(--badge-border-secondary)` → `transparent`
    - `var(--badge-background-secondary-hover)` → `#f5f5f5`
    - `var(--badge-foreground-secondary-hover)` → `#171717`
    - `var(--badge-border-secondary-hover)` → `transparent`
- **`outline`**
  - _Use:_ Low-emphasis tag for categorical metadata.
  - base: `bg-[var(--badge-background-outline)]` · `dark:bg-[var(--badge-background-outline)]` · `text-[var(--badge-foreground-outline)]` · `dark:text-[var(--badge-foreground-outline)]` · `border-[var(--badge-border-outline)]` · `dark:border-[var(--badge-border-outline)]` · `dark:hover:bg-[var(--badge-background-outline-hover)]` · `dark:hover:text-[var(--badge-foreground-outline-hover)]` · `dark:hover:border-[var(--badge-border-outline-hover)]`
  - hover: `bg-[var(--badge-background-outline-hover)]` · `text-[var(--badge-foreground-outline-hover)]` · `border-[var(--badge-border-outline-hover)]`
  - resolved tokens (6 total):
    - `var(--badge-background-outline)` → `#ffffff`
    - `var(--badge-foreground-outline)` → `#0a0a0a`
    - `var(--badge-border-outline)` → `#e5e5e5`
    - `var(--badge-background-outline-hover)` → `#f5f5f5`
    - `var(--badge-foreground-outline-hover)` → `#737373`
    - `var(--badge-border-outline-hover)` → `#e5e5e5`
- **`ghost`**
  - _Use:_ Minimal text-only tag for inline annotations.
  - base: `bg-[var(--badge-background-ghost)]` · `dark:bg-[var(--badge-background-ghost)]` · `text-[var(--badge-foreground-ghost)]` · `dark:text-[var(--badge-foreground-ghost)]` · `border-[var(--badge-border-ghost)]` · `dark:border-[var(--badge-border-ghost)]` · `dark:hover:bg-[var(--badge-background-ghost-hover)]` · `dark:hover:text-[var(--badge-foreground-ghost-hover)]` · `dark:hover:border-[var(--badge-border-ghost-hover)]`
  - hover: `bg-[var(--badge-background-ghost-hover)]` · `text-[var(--badge-foreground-ghost-hover)]` · `border-[var(--badge-border-ghost-hover)]`
  - resolved tokens (6 total):
    - `var(--badge-background-ghost)` → `transparent`
    - `var(--badge-foreground-ghost)` → `#0a0a0a`
    - `var(--badge-border-ghost)` → `transparent`
    - `var(--badge-background-ghost-hover)` → `#f5f5f5`
    - `var(--badge-foreground-ghost-hover)` → `#737373`
    - `var(--badge-border-ghost-hover)` → `transparent`
- **`link`**
  - _Use:_ Clickable tag behaving as navigation.
  - base: `bg-[var(--badge-background-link)]` · `dark:bg-[var(--badge-background-link)]` · `text-[var(--badge-foreground-link)]` · `dark:text-[var(--badge-foreground-link)]` · `border-[var(--badge-border-link)]` · `dark:border-[var(--badge-border-link)]` · `dark:hover:bg-[var(--badge-background-link-hover)]` · `dark:hover:text-[var(--badge-foreground-link-hover)]` · `dark:hover:border-[var(--badge-border-link-hover)]` · `underline-offset-4`
  - hover: `bg-[var(--badge-background-link-hover)]` · `text-[var(--badge-foreground-link-hover)]` · `border-[var(--badge-border-link-hover)]` · `underline`
  - resolved tokens (6 total):
    - `var(--badge-background-link)` → `transparent`
    - `var(--badge-foreground-link)` → `#171717`
    - `var(--badge-border-link)` → `transparent`
    - `var(--badge-background-link-hover)` → `transparent`
    - `var(--badge-foreground-link-hover)` → `#171717`
    - `var(--badge-border-link-hover)` → `transparent`
- **`tertiary`**
  - _Use:_ Alternative emphasis, softer than secondary.
  - base: `bg-[var(--badge-background-tertiary)]` · `dark:bg-[var(--badge-background-tertiary)]` · `text-[var(--badge-foreground-tertiary)]` · `dark:text-[var(--badge-foreground-tertiary)]` · `border-[var(--badge-border-tertiary)]` · `dark:border-[var(--badge-border-tertiary)]` · `dark:hover:bg-[var(--badge-background-tertiary-hover)]` · `dark:hover:text-[var(--badge-foreground-tertiary-hover)]` · `dark:hover:border-[var(--badge-border-tertiary-hover)]`
  - hover: `bg-[var(--badge-background-tertiary-hover)]` · `text-[var(--badge-foreground-tertiary-hover)]` · `border-[var(--badge-border-tertiary-hover)]`
  - resolved tokens (6 total):
    - `var(--badge-background-tertiary)` → `#93c5fd`
    - `var(--badge-foreground-tertiary)` → `#0a0a0a`
    - `var(--badge-border-tertiary)` → `transparent`
    - `var(--badge-background-tertiary-hover)`
    - `var(--badge-foreground-tertiary-hover)` → `#0a0a0a`
    - `var(--badge-border-tertiary-hover)` → `transparent`

**`size` dimension**

- **`xs`**
  - _Use:_ Tightest badge size — inline with 12-13px text.
  - base: `h-[var(--badge-height-xs)]` · `rounded-full` · `text-[length:var(--badge-font-size-xs)]` · `px-[var(--badge-padding-x-xs)]` · `gap-[var(--badge-gap-xs)]` · `has-data-[icon=inline-start]:pl-1.5` · `has-data-[icon=inline-end]:pr-1.5`
  - resolved tokens (4 total):
    - `var(--badge-height-xs)` → `1.25rem`
    - `var(--badge-font-size-xs)` → `0.75rem`
    - `var(--badge-padding-x-xs)` → `0.5rem`
    - `var(--badge-gap-xs)` → `0.25rem`
- **`sm`**
  - _Use:_ Compact badge alongside table cells or list rows.
  - base: `h-[var(--badge-height-sm)]` · `rounded-full` · `text-[length:var(--badge-font-size-sm)]` · `px-[var(--badge-padding-x-sm)]` · `gap-[var(--badge-gap-sm)]` · `has-data-[icon=inline-start]:pl-1.5` · `has-data-[icon=inline-end]:pr-1.5`
  - resolved tokens (4 total):
    - `var(--badge-height-sm)` → `1.5rem`
    - `var(--badge-font-size-sm)` → `0.875rem`
    - `var(--badge-padding-x-sm)` → `0.75rem`
    - `var(--badge-gap-sm)` → `0.25rem`
- **`md`** _(default)_
  - _Use:_ Default badge size for most inline use.
  - base: `h-[var(--badge-height-md)]` · `rounded-full` · `text-[length:var(--badge-font-size-md)]` · `px-[var(--badge-padding-x-md)]` · `gap-[var(--badge-gap-md)]` · `has-data-[icon=inline-start]:pl-2` · `has-data-[icon=inline-end]:pr-2`
  - resolved tokens (4 total):
    - `var(--badge-height-md)` → `1.75rem`
    - `var(--badge-font-size-md)` → `0.875rem`
    - `var(--badge-padding-x-md)` → `0.75rem`
    - `var(--badge-gap-md)` → `0.5rem`
- **`lg`**
  - _Use:_ Prominent badge in card headers or dashboard KPIs.
  - base: `h-[var(--badge-height-lg)]` · `rounded-full` · `text-[length:var(--badge-font-size-lg)]` · `px-[var(--badge-padding-x-lg)]` · `gap-[var(--badge-gap-lg)]` · `has-data-[icon=inline-start]:pl-3` · `has-data-[icon=inline-end]:pr-3`
  - resolved tokens (4 total):
    - `var(--badge-height-lg)` → `2rem`
    - `var(--badge-font-size-lg)` → `1rem`
    - `var(--badge-padding-x-lg)` → `1rem`
    - `var(--badge-gap-lg)` → `0.5rem`
- **`xl`**
  - _Use:_ Hero or spotlight badge — rarely used.
  - base: `h-[var(--badge-height-xl)]` · `rounded-full` · `text-[length:var(--badge-font-size-xl)]` · `px-[var(--badge-padding-x-xl)]` · `gap-[var(--badge-gap-xl)]` · `has-data-[icon=inline-start]:pl-3` · `has-data-[icon=inline-end]:pr-3`
  - resolved tokens (4 total):
    - `var(--badge-height-xl)` → `2.25rem`
    - `var(--badge-font-size-xl)` → `1rem`
    - `var(--badge-padding-x-xl)` → `1rem`
    - `var(--badge-gap-xl)` → `0.5rem`
- **`icon`**
  - base: `size-[var(--badge-icon-md)]` · `rounded-full`
  - resolved tokens (2 total):
    - `var(--badge-icon-md)` → `1.75rem`
    - `var(--badge-font-size-md)` → `0.875rem`
- **`icon-xs`**
  - base: `size-[var(--badge-icon-xs)]` · `rounded-full`
  - resolved tokens (2 total):
    - `var(--badge-icon-xs)` → `1.25rem`
    - `var(--badge-font-size-xs)` → `0.75rem`
- **`icon-sm`**
  - base: `size-[var(--badge-icon-sm)]` · `rounded-full`
  - resolved tokens (2 total):
    - `var(--badge-icon-sm)` → `1.5rem`
    - `var(--badge-font-size-sm)` → `0.875rem`
- **`icon-lg`**
  - base: `size-[var(--badge-icon-lg)]` · `rounded-full`
  - resolved tokens (2 total):
    - `var(--badge-icon-lg)` → `2rem`
    - `var(--badge-font-size-lg)` → `1rem`
- **`icon-xl`**
  - base: `size-[var(--badge-icon-xl)]` · `rounded-full`
  - resolved tokens (2 total):
    - `var(--badge-icon-xl)` → `2.25rem`
    - `var(--badge-font-size-xl)` → `1rem`

**Also in this category:**

- **Kbd** — variants: none
- **Tag** — variants: variant(4) · default: `{"variant":"default"}`

### Navigation

**Representative component** — use the scheme below as the template for every other
component in this category.

#### Tabs

Source: `packages/ui-foundation/src/components/ui/tabs/tabs.variants.ts`

Layout: gap: `var(--tabs-gap)` → `0.5rem`

Default: `{"variant":"default"}`

**`variant` dimension**

- **`default`** _(default)_
  - _Use:_ Standard tab bar with underline indicator — primary page navigation.
  - base: `border` · `bg-[var(--tabs-list-background-default)]` · `border-[var(--tabs-list-border-default)]` · `gap-[var(--tabs-list-gap-default)]` · `p-[var(--tabs-list-padding-default)]` · `rounded-[var(--tabs-list-radius-default)]`
  - resolved tokens (5 total):
    - `var(--tabs-list-background-default)` → `#09090b`
    - `var(--tabs-list-border-default)` → `transparent`
    - `var(--tabs-list-gap-default)` → `1rem`
    - `var(--tabs-list-padding-default)` → `1rem`
    - `var(--tabs-list-radius-default)` → `0.3125rem`
- **`line`**
  - base: `border-b` · `bg-[var(--tabs-list-background-line)]` · `border-[var(--tabs-list-border-line)]` · `gap-[var(--tabs-list-gap-line)]` · `p-[var(--tabs-list-padding-line)]` · `pb-0` · `rounded-t-[var(--tabs-list-radius-line)]` · `rounded-b-none`
  - resolved tokens (5 total):
    - `var(--tabs-list-background-line)` → `transparent`
    - `var(--tabs-list-border-line)` → `#e5e5e5`
    - `var(--tabs-list-gap-line)` → `0.75rem`
    - `var(--tabs-list-padding-line)` → `1.25rem`
    - `var(--tabs-list-radius-line)` → `0px`
- **`outline`**
  - base: `border-b` · `border-[var(--tabs-list-border-outline)]` · `bg-[var(--tabs-list-background-outline)]` · `gap-[var(--tabs-list-gap-outline)]` · `p-[var(--tabs-list-padding-outline)]` · `pb-0` · `rounded-t-[var(--tabs-list-radius-outline)]` · `rounded-b-none`
  - resolved tokens (5 total):
    - `var(--tabs-list-border-outline)` → `#e5e5e5`
    - `var(--tabs-list-background-outline)` → `transparent`
    - `var(--tabs-list-gap-outline)` → `0.75rem`
    - `var(--tabs-list-padding-outline)` → `1.25rem`
    - `var(--tabs-list-radius-outline)` → `0px`

**Also in this category:**

- **Breadcrumb** — variants: none
- **Menubar** — variants: none
- **NavigationMenu** — variants: none
- **Pagination** — variants: none
- **Sidebar** — variants: variant(2), size(3), isActive(1) · default: `{"variant":"default","size":"default"}`

### Distinctive Components

**Representative component** — use the scheme below as the template for every other
component in this category.

#### Calendar

Source: `packages/ui-foundation/src/components/ui/calendar/calendar.variants.ts`

Layout: border: 1px solid

_This component has no CVA variants — styling is structural only._

**Also in this category:**

- **FullCalendar** — variants: none
- **Tree** — variants: none
- **DataTable** — variants: hovered(2)
- **WorkflowDesigner** — variants: none

> For the full per-component reference — every variant's classes, CSS tokens,
> and Brand Overlay differences — see [vortex-components.md](./vortex-components.md).

## 5. Layout Principles

Vortex layouts assume dense information architecture — dashboards, data tables, admin consoles,
booking calendars — where vertical rhythm and horizontal alignment matter more than decorative
whitespace. The system is 4px-based (`--spacing: 0.25rem`) with 8px as the de-facto minimum
grid unit for padding and gap; component-local spacing rarely drops below `--spacing * 2` except
inside badges and inline-code.

**Grid & container philosophy:**

- Top-level page layout uses a centered container capped at `1400px` (breakpoint `2xl`) with
  32px horizontal padding. Dashboards may break out of the container for full-width tables.
- Internal structure is expressed via Tailwind utility classes (`flex`, `grid`, `gap-*`) rather
  than dedicated grid components. There is no "Grid" or "Col" primitive; layout lives in the
  consuming app.
- Cards and sections use consistent inner padding (`--card-content-padding-x`) so that nested
  content aligns across the viewport regardless of section depth.
- Form layouts prefer vertical stacking with 16px gaps between fields; horizontal two-column
  forms unlock at `md` (768px) and above.

**Whitespace discipline:**

- Section-to-section gaps default to 48px on desktop, compressing to 32px on tablet and 24px on
  mobile.
- Inter-component gaps within a section default to 16–24px depending on density.
- Inline element gaps (icon + text, chip + chip) default to 4–8px via the `gap-*` variant token.
- The system does not use ornamental whitespace — generous padding is reserved for primary
  CTAs and hero moments, not every surface.

**Structural separation:**

- Borders (`1px solid var(--border)`) carry most structural weight — dividers between table
  rows, card edges, nav boundaries.
- Shadows are reserved for elevation (popovers, dialogs, dropdowns, sticky headers) — never for
  ornamental depth on static surfaces.
- Alternating neutral surfaces (`--background` vs `--card` vs `--muted`) create implicit
  sections without requiring borders in all cases.

### Spacing Scale

Base unit: `0.25rem`.

| Step | Value |
| --- | --- |
| 0 | `0` |
| 1 | `calc(0.25rem * 1)` |
| 2 | `calc(0.25rem * 2)` |
| 3 | `calc(0.25rem * 3)` |
| 4 | `calc(0.25rem * 4)` |
| 5 | `calc(0.25rem * 5)` |
| 6 | `calc(0.25rem * 6)` |
| 8 | `calc(0.25rem * 8)` |
| 10 | `calc(0.25rem * 10)` |
| 12 | `calc(0.25rem * 12)` |
| 0.5 | `calc(0.25rem * 0.5)` |

## 6. Depth & Elevation

Vortex uses a six-step shadow scale with low-opacity blacks. Shadows are
applied sparingly — structural separation is usually expressed through borders
and neutral surface alternation rather than elevation.

### Shadow Scale

| Step | Value → Resolved |
| --- | --- |
| --shadow-xs | `0px 1px 3px 0px var(--color-shadow-xs)` → `0px 1px 3px 0px #0000001d` |
| --shadow-sm | `0px 1px 2px 0px var(--color-shadow-sm)` → `0px 1px 2px 0px #0000001a` |
| --shadow-md | `0px 4px 6px -1px var(--color-shadow-md)` → `0px 4px 6px -1px #0000001a` |
| --shadow-lg | `0px 10px 15px -3px var(--color-shadow-lg)` → `0px 10px 15px -3px #0000001a` |
| --shadow-xl | `0px 20px 25px -5px var(--color-shadow-xl)` → `0px 20px 25px -5px #0000001a` |
| --shadow-2xl | `0px 25px 50px -12px var(--color-shadow-2xl)` → `0px 25px 50px -12px #00000040` |

### Radius Scale

| Step | Value |
| --- | --- |
| --radius-none | `0px` |
| --radius-xs | `0.0781rem` |
| --radius-sm | `0.1563rem` |
| --radius-md | `0.2344rem` |
| --radius-lg | `0.3125rem` |
| --radius-xl | `0.4688rem` |
| --radius-2xl | `0.625rem` |
| --radius-3xl | `0.9375rem` |
| --radius-4xl | `1.25rem` |
| --radius-full | `999px` |

### Focus Ring

Standard focus affordance: `focus-visible:ring-2 focus-visible:ring-offset-2`.
The ring color resolves to `--ring` (defaults to `--primary` in Foundation).
**Figma**: apply a `2px` outside stroke with color `--ring` and `2px` offset on
all interactive component variants.

| Token | Value → Resolved |
| --- | --- |
| `--ring` | `var(--color-neutral-400)` → `#a3a3a3` |

## 7. Do's and Don'ts

### Do

- **Use semantic role tokens (`--primary`, `--destructive`, `--muted`) instead of primitive color
  tokens (`--color-blue-500`).** Role tokens automatically pick up Brand Overlay overrides; raw
  primitives do not.
- **Compose components with CVA variants, not inline overrides.** If a variant doesn't exist for
  what you need, add it to the `.variants.ts` file — don't patch it with `className` at the call
  site.
- **Reserve accent colors (`--primary`, `--destructive`) for interactive elements and critical
  states.** Backgrounds, borders, and structural elements should stay in the neutral scale or
  `--muted` family.
- **Use the five-role status scale (`--status-info` / `success` / `warning` / `error`) for system
  feedback.** Mixing raw green/red/yellow hex values breaks Brand Overlay consistency.
- **Let Korean text breathe.** Default `letter-spacing: 0em` and `font-size: 14px` body is
  calibrated for Noto Sans KR; don't tighten tracking below `-0.01em` for body text.
- **Respect the 10-step radius scale.** Every UI surface should have radius from the scale, not
  a custom pixel value; `--radius-full` for pills, `--radius-sm` through `--radius-2xl` for
  cards and inputs.
- **Apply shadows sparingly and only for true elevation** — dropdowns, dialogs, sticky headers,
  popovers. Static cards use borders, not shadows.

### Don't

- **Don't hard-code hex values in component styles.** Every color must come from a token,
  whether primitive (`--color-blue-500`), role (`--primary`), or component-scoped (`--button-*`).
- **Don't bypass the CVA pattern with ad-hoc `className` conditionals.** If a component needs a
  new look, add a variant; if a variant only appears once, question whether it belongs as a
  variant at all.
- **Don't assume light mode.** Dark mode tokens exist in `.dark { ... }` blocks; components
  must reference role tokens (which auto-switch) not light-only primitives.
- **Don't stack more than two shadows on an element.** Visual clutter defeats the purpose of
  elevation cues.
- **Don't override Brand Overlay semantics.** If a Cals surface needs non-booking colors, use
  Foundation role tokens, not arbitrary overrides that erode the brand system.
- **Don't mix chart colors arbitrarily.** Use `--chart-1` through `--chart-5` in order;
  deviating from the palette makes dashboards visually inconsistent across products.
- **Don't use Tailwind arbitrary values (`bg-[#ff0000]`) in component source.** The
  `bg-[var(--button-background-default)]` pattern is the established convention.

## 8. Responsive Behavior

### Breakpoints

| Name | Min Width |
| --- | --- |
| sm | `640px` |
| md | `768px` |
| lg | `1024px` |
| xl | `1280px` |
| 2xl | `1400px` |

Vortex components are mobile-first: all variants assume single-column stacking
at `sm` and below, with structural layout revealing at `md` (768px) and above.
Touch targets maintain a minimum of 40px at all sizes.

## 9. Agent Prompt Guide

### Preset Selection

For the selection algorithm, `notFor` exclusion, tie-break rules, and fallback,
see [§12 — Service Preset Auto-Selection](#12-service-preset-auto-selection).
§1's Service Presets table and Brand Voice are the lookup catalog;
[vortex-presets.md](./vortex-presets.md) has each preset's full overview and concept.

### Preset Application — how to inject (★ `!important` required)

After selecting a preset, inject its primary palette into the generated page's `:root` —
**lay down the full 50~950 scale and put `!important` on every token**:

```css
:root {
  --color-primary-50: <preset 50> !important;
  --color-primary-100: <preset 100> !important;
  --color-primary-200: <preset 200> !important;
  --color-primary-300: <preset 300> !important;
  --color-primary-400: <preset 400> !important;
  --color-primary-500: <preset 500> !important; /* signature / CTA */
  --color-primary-600: <preset 600> !important;
  --color-primary-700: <preset 700> !important;
  --color-primary-800: <preset 800> !important;
  --color-primary-900: <preset 900> !important; /* chrome / sidebar */
  --color-primary-950: <preset 950> !important;
  --primary: var(--color-primary-500) !important;
  --primary-foreground: <preset fg> !important;
  --ring: var(--color-primary-500) !important;
}
```

**Why `!important` is mandatory**: the design-system bundle (`_ds_bundle.css`, neutral palette) is
loaded _after_ the preset `:root` (via `ds-base.js` in slide/doc templates), so without `!important`
the bundle's neutral `--color-primary-*` overrides the preset and the brand color collapses to gray.
Always inject the **full 50~950 palette** — charts, badges, and soft backgrounds use 200~400 too.

---

When generating UI in Vortex style, always:

1. **Reference semantic role tokens, not raw colors.** Use `var(--primary)`, `var(--destructive)`,
   `var(--muted-foreground)` — never hex values. Role tokens auto-adapt to Brand Overlays.
2. **Use the CVA component pattern.** Components take `variant` and `size` props (both string
   literals from the declared variant set); never use inline `style` or ad-hoc `className`
   beyond `cn()` composition.
3. **Apply the 10-step radius scale.** `--radius-sm` through `--radius-2xl` for surfaces,
   `--radius-full` for pills. No custom pixel values.
4. **Use Noto Sans KR as the default font** — it's declared at `:root { --font-sans }`. Body
   text at 14px with relaxed leading; headlines tight-tracked.
5. **Reserve shadows for elevation** (popovers, dropdowns, dialogs). Static cards use `1px
solid var(--border)` instead.

### Example component prompts

- "Create a Vortex dashboard card: `bg-card`, `border border-border`, `rounded-lg`, `p-6`.
  Title at 20px font-weight 600 `text-foreground`. Body at 14px `text-muted-foreground`. A
  primary `Button variant='default' size='sm'` in the bottom-right corner."

- "Build a Vortex booking status badge (Cals overlay): compact pill with `rounded-full`,
  `px-2 py-0.5`, 11px font-weight 500. Five color variants mapping to
  `--color-booking-{available|pending|confirmed|cancelled|completed}` backgrounds with white
  foreground."

- "Design a Vortex data table header row: `bg-muted` background, `border-b border-border`,
  text `text-muted-foreground` at 12px font-weight 600 uppercase `tracking-wide`. Each cell
  `px-4 py-3` with left-aligned text, optional sort chevron on hover. Sticky positioning at
  `top: 0`."

- "Create a Vortex form field group: vertical `flex flex-col gap-2`. Label at 13px font-weight
  500 `text-foreground`. `Input size='md'` below with `w-full`. Helper text at 12px
  `text-muted-foreground`. Error state swaps to `text-destructive` and `border-destructive` on
  the input."

- "Build an iCignal analytics KPI card: square-ish `aspect-[4/3]`, `bg-card`, `border
border-border`, `rounded-md`, `p-6`. Metric label at 12px `text-muted-foreground` uppercase.
  Metric value at 32px font-weight 700 `text-foreground`. Trend chip below using
  `--chart-1` (positive) or `--chart-5` (negative) background with white foreground."

### Iteration guide

1. If a color doesn't fit the neutral scale or a semantic role, question whether it belongs at
   all. Most accents should collapse to `--primary` or a `--status-*` role.
2. Size tokens (`xs`, `sm`, `md`, `lg`, `xl`) are ordered — never skip levels (e.g. don't jump
   from `sm` to `lg` within a form without a structural reason).
3. Dark mode is handled automatically if you use role tokens. If something looks wrong in
   `.dark`, you're probably using a light-only primitive.
4. Brand Overlays never change component structure — only token values. If your component only
   works in one brand, it's not a Vortex component.
5. When in doubt, consult `vortex-components.md` for the exact variant contract of any
   component before composing it.

## 10. Figma Variables Mapping

Vortex tokens are exported as machine-readable JSON for tooling integration.
Use the JSON files as the source of truth for any importer or pipeline; this
section is a high-level index of the collections only.

### Files

- `vortex-dtcg.json` — W3C DTCG format (Tokens Studio, Style Dictionary). References preserved (`{token.path}`).
- `vortex-figma-variables/light.json`, `vortex-figma-variables/dark.json` — Figma Variables Import format, one file per mode. All values resolved (sRGB / px).

### Collections

| Collection | Modes | Tokens | Source |
| --- | --- | --- | --- |
| Colors | `light` / `dark` | 35 | `color.semantic.*` |
| Brand | `foundation` / `cals` / `icignal` | Cals: 0, iCignal: 31 | `overlays.*.colorDeltas` |
| Typography | — | 8 sizes + weights + line-heights | `typography.*` |
| Spacing | — | 11 steps (base 0.25rem) | `spacing.*` |
| Shape | — | 10 radius + 6 shadow | `radius.*`, `shadow.*` |

Foundation mode shares values with the Colors collection; `cals` / `icignal` apply
their delta tokens on top. To consume the values, fetch the JSON file (see Distribution
in README) and import via Figma `Variables → Import`.

## 11. References

This document is the generated AI-readable catalog of the Vortex Design System. It pairs with
the `vortex-components.md` full component reference (same directory), the live Storybook, and
the Vortex documentation site. Token values are extracted at build time from the Foundation,
Cals, and iCignal workspace packages — regenerated on every commit that touches a CSS token
file or a `.variants.ts` file.

- Full component reference: [vortex-components.md](./vortex-components.md)
- Service preset reference: [vortex-presets.md](./vortex-presets.md)
- Storybook: https://foundation.storybook.vortexui.com
- Design docs: https://vortexui.com
- CLI: `npx @vortex/cli design-md`

## 12. Service Preset Auto-Selection

이 문서(`vortex.md`) 하나만 fetch한 상태에서, 사용자 요청에 적합한 service preset을
자동 선정해 결과에 반영하기 위한 절차입니다. 카탈로그 본체는
[Section 1 — Service Presets](#service-presets)에 있고, 각 preset의 전체 overview·
concept(키워드·톤·primary palette 전체)은 별도 파일
[vortex-presets.md](./vortex-presets.md)에서 fetch합니다.

### Fetch URL

Service preset 본문은 단일 파일로 배포됩니다. preset id별 개별 URL이 아니라,
**아래 URL 하나만 fetch한 뒤 문서 내 `## <preset-id>` 섹션을 사용**합니다.

- Hosted: `https://vortexui.com/design-md/vortex-presets.md`
- Local (npm/CLI): `./vortex-presets.md` (`vortex.md`와 동일 디렉토리)

용량은 약 20K 이하로 추가 fetch 비용은 무시할 수 있습니다.

### Selection Protocol

1. 사용자 요청에서 **도메인 키워드**를 추출합니다 (예: "골프 예약 앱" → 스포츠·레저).
2. Section 1의 Service Presets 표에서 `domains` / `match for`와 매칭되는 후보를 찾습니다.
3. **`not for`에 해당하는 도메인이 1개라도 명시되면 그 preset은 후보에서 제외**합니다.
4. 후보가 복수면, 도메인 교집합 크기가 큰 쪽 → 동률이면 `match for` 문장과의 의미 일치도가 높은 쪽을 선택합니다.
5. 선택한 preset id로 `vortex-presets.md`를 fetch하고, 해당 `## <preset-id>` 섹션의
   primary palette + keywords + 톤앤매너를 base vortex 토큰 위에 적용합니다.

### Fallback

- 요청에 도메인 단서가 없거나(예: "버튼 만들어줘") 후보가 0개이면
  **preset 없이 base vortex 토큰만** 사용합니다. 추측으로 브랜드 색을 입히지 마세요.
- 요청이 명시적으로 base brand(`cals` / `icignal`) 자체를 가리키면
  service preset이 아닌 [Brand Voice](#brand-voice) 섹션을 사용합니다.

### Worked Example

> 요청: "스포츠 관련 랜딩 페이지 카드 컴포넌트 만들어줘"
>
> 1. 도메인 키워드: `스포츠`
> 2. Section 1 표에서 `icignal-amber`의 `domains`에 `스포츠` 포함 → 후보
> 3. `not for` 검사 통과
> 4. 단일 후보 → `icignal-amber` 선정
> 5. `vortex-presets.md`를 fetch하고 `## icignal-amber` 섹션의 primary palette/톤을 base 토큰 위에 적용
