---
name: 구름
design_system_name: Vapor UI
slug: vapor-ui
category: developer
last_updated: "2026-06-01"
created_at: "2026-05-10"
sources:
- https://vapor-ui.goorm.io/
- https://blog.goorm.io/vapor-figma-seoul/
- https://www.figma.com/community/file/1508829832204351721/vapor-design-system
- https://www.npmjs.com/package/@vapor-ui/core
- https://github.com/goorm-dev/vapor-ui
related_services: []
lang: ko
logo: https://getdesign.kr/logos/goorm.png
---
# Vapor UI — design.md
> 한국의 클라우드 IDE/개발자 교육 회사 goorm(구름)이 운영하는 디자인 시스템. goormIDE·goormEDU·goormLEVEL·goorm Cloud·goorm.co 마케팅 사이트가 단일 시각 언어를 공유하도록 설계되었다 [src:1]. 본 문서는 사용자가 제공한 Claude Design 핸드오프 번들(업데이트본)을 1차 출처로 추출·압축 해제하여 합성한 결과다.
## Brand & Style
Vapor는 시스템 자체의 정체성을 **opinionated, light-first, very token-driven, proudly Korean-bilingual** 로 정의한다 [src:5]. opinionated는 디자인 결정이 토큰 레벨에서 강하게 박혀 있다는 뜻이고, light-first는 다크 모드가 동등하게 지원되되 1차 환경은 화이트 캔버스라는 의미다. very token-driven은 product-facing 색상이 모두 시맨틱 alias로만 노출되어 raw 팔레트는 새 role을 만들 때만 직접 참조된다는 정책을 가리킨다 [src:5].
대상 사용자는 세 층위다. 디자이너에게는 Figma 라이브러리(`figma.com/community/file/1508829832204351721`)를, 개발자에게는 `@vapor-ui/core` 1.3.0을 중심으로 한 6패키지 모노레포(`goorm-dev/vapor-ui`)를, 그리고 product surface를 운영하는 host 팀에게는 `@vapor-ui/css-generator`를 통해 기본 키 팔레트를 자체 브랜드 색으로 교체할 수 있는 토큰 빌드 파이프라인을 제공한다 [src:4][src:5]. 2025년 4월 전담 조직 Vapor Squad가 신설되어 Squad Lead 최준영, CDO 이태성 체제로 운영된다 [src:2].
전체 무드는 **bright, soft white** 로 요약된다 [src:5]. 정보 밀도가 높은 표면(테이블·코드 패널·폼)은 흰 카드 위에 옅은 페이지 배경, 그리고 1px 헤어라인으로 분리되는 패턴이며, 색은 절제되어 있고 시맨틱 팔레트는 상태(primary CTA, success/warning/danger badge, link blue)에만 사용된다 [src:5]. 시스템 표면 자체에는 **그라디언트, 글래스/블러 효과, 텍스처, 장식 일러스트가 없다** [src:5]. 그라디언트가 등장하는 단 두 곳은 Vapor 워드마크 로고와 Getting Started 페이지의 컨셉 히어로다 [src:5]. 트랜스페어런시·블러는 토스트와 다이얼로그 스크림에만 쓰이고 frosted glass나 `backdrop-filter`는 시스템에서 제외된다 [src:5].
Voice는 "factual, didactic, slightly warm"으로 정의된다 [src:5]. 한국어가 1차 언어이고 헤딩과 본문은 존댓말(~니다/~습니다), 버튼은 짧은 명령형 동사("저장", "삭제", "닫기")로 작성한다 [src:5]. 영문 큰 타이틀과 한 줄 한국어 서브타이틀을 페어링하는 bilingual 패턴이 표준이며(예: `Overview` / `Vapor와 우리의 핵심철학을 소개합니다`), 영문 헤딩은 Title Case, 토큰 namespace는 모두 kebab-case 소문자로 통일된다 [src:5]. 본 카탈로그 메타 문서는 Vapor 자체 카피와 달리 `~다` 평서체로 기술하며, Vapor의 존댓말 정책은 product surface 카피에 한해 적용되는 규칙임을 분리해 둔다.
## Colors
Vapor는 두 테마(`light`, `dark`) 위에 11-family 베이스 팔레트를 둔다 — Red, Pink, Grape, Violet, Blue, Cyan, Green, Lime, Yellow, Orange, Gray. 각 패밀리는 `050, 100, 200, 300, 400, 500, 600, 700, 800, 900` 10단계 + `color-white`/`color-black` 상수로 구성된다 [src:5]. 브랜드 primary는 **Blue 500**이며, 카노니컬한 violet은 Vapor 워드마크에만 등장한다 [src:5].
product-facing 색은 모두 **시맨틱 alias**로 호출하고 raw 팔레트는 새 role을 만들 때만 직접 노출된다 — 명명 규칙은 `color-{role}-{intent}-{level}`이며 roles는 `background`/`foreground`/`border`, intents는 `primary, secondary, success, warning, danger, hint, contrast, normal`, level은 `100`(soft) / `200`(strong)이다 [src:5].
### Base palette (11 family × 10 step)
```yaml
# Gray — 표면·텍스트·디바이더의 기반
gray-050: oklch(0.984 0.000 0)
gray-100: oklch(0.972 0.000 0)
gray-200: oklch(0.892 0.000 0)
gray-300: oklch(0.804 0.000 0)
gray-400: oklch(0.712 0.000 0)
gray-500: oklch(0.658 0.000 0)
gray-600: oklch(0.453 0.000 0)
gray-700: oklch(0.314 0.000 0)
gray-800: oklch(0.234 0.000 0)
gray-900: oklch(0.169 0.000 0)
# Blue — 브랜드 primary, link, focus ring 앵커
blue-050: oklch(0.974 0.018 254)
blue-100: oklch(0.901 0.052 254)
blue-200: oklch(0.781 0.110 264)
blue-300: oklch(0.690 0.146 261)
blue-400: oklch(0.638 0.166 258)
blue-500: oklch(0.581 0.181 254) # 브랜드 primary
blue-600: oklch(0.560 0.196 257)
blue-700: oklch(0.451 0.166 259)
blue-800: oklch(0.348 0.142 263)
blue-900: oklch(0.232 0.156 270)
# Cyan
cyan-050: oklch(0.978 0.013 215)
cyan-100: oklch(0.911 0.039 220)
cyan-200: oklch(0.812 0.078 224)
cyan-300: oklch(0.703 0.105 226)
cyan-400: oklch(0.633 0.110 230)
cyan-500: oklch(0.567 0.107 230)
cyan-600: oklch(0.475 0.099 232)
cyan-700: oklch(0.388 0.080 233)
cyan-800: oklch(0.309 0.061 233)
cyan-900: oklch(0.255 0.057 232)
# Green — success
green-050: oklch(0.978 0.022 156)
green-100: oklch(0.913 0.075 154)
green-200: oklch(0.831 0.117 153)
green-300: oklch(0.726 0.158 151)
green-400: oklch(0.652 0.157 154)
green-500: oklch(0.566 0.114 167)
green-600: oklch(0.467 0.094 169)
green-700: oklch(0.376 0.075 169)
green-800: oklch(0.294 0.067 170)
green-900: oklch(0.246 0.057 168)
# Lime
lime-050: oklch(0.978 0.044 122)
lime-100: oklch(0.926 0.116 124)
lime-200: oklch(0.857 0.158 127)
lime-300: oklch(0.787 0.184 130)
lime-400: oklch(0.689 0.187 131)
lime-500: oklch(0.566 0.158 132)
lime-600: oklch(0.464 0.130 132)
lime-700: oklch(0.371 0.106 133)
lime-800: oklch(0.292 0.085 133)
lime-900: oklch(0.225 0.078 134)
# Yellow / Amber
yellow-050: oklch(0.971 0.044 79)
yellow-100: oklch(0.906 0.111 84)
yellow-200: oklch(0.847 0.158 79)
yellow-300: oklch(0.768 0.166 65)
yellow-400: oklch(0.660 0.158 60)
yellow-500: oklch(0.560 0.142 56)
yellow-600: oklch(0.456 0.121 55)
yellow-700: oklch(0.371 0.099 56)
yellow-800: oklch(0.318 0.081 53)
yellow-900: oklch(0.275 0.083 53)
# Orange — warning
orange-050: oklch(0.957 0.025 30)
orange-100: oklch(0.872 0.072 32)
orange-200: oklch(0.778 0.131 34)
orange-300: oklch(0.703 0.166 36)
orange-400: oklch(0.628 0.190 38)
orange-500: oklch(0.557 0.179 38)
orange-600: oklch(0.464 0.169 39)
orange-700: oklch(0.385 0.150 41)
orange-800: oklch(0.346 0.135 41)
orange-900: oklch(0.300 0.119 41)
# Red — danger
red-050: oklch(0.957 0.020 17)
red-100: oklch(0.881 0.057 18)
red-200: oklch(0.795 0.115 18)
red-300: oklch(0.687 0.180 19)
red-400: oklch(0.638 0.213 19)
red-500: oklch(0.577 0.198 21)
red-600: oklch(0.488 0.181 22)
red-700: oklch(0.405 0.181 25)
red-800: oklch(0.330 0.169 28)
red-900: oklch(0.255 0.144 28)
# Pink
pink-050: oklch(0.957 0.018 13)
pink-100: oklch(0.870 0.054 5)
pink-200: oklch(0.770 0.117 6)
pink-300: oklch(0.652 0.176 7)
pink-400: oklch(0.567 0.179 8)
pink-500: oklch(0.510 0.180 8)
pink-600: oklch(0.418 0.168 8)
pink-700: oklch(0.339 0.158 8)
pink-800: oklch(0.296 0.146 9)
pink-900: oklch(0.279 0.137 8)
# Grape
grape-050: oklch(0.954 0.024 308)
grape-100: oklch(0.866 0.087 312)
grape-200: oklch(0.755 0.166 314)
grape-300: oklch(0.633 0.225 316)
grape-400: oklch(0.546 0.230 316)
grape-500: oklch(0.475 0.215 314)
grape-600: oklch(0.391 0.193 313)
grape-700: oklch(0.317 0.169 312)
grape-800: oklch(0.279 0.158 311)
grape-900: oklch(0.260 0.157 313)
# Violet — 워드마크 전용 카노니컬 violet (violet-300)
violet-050: oklch(0.946 0.040 296)
violet-100: oklch(0.853 0.121 297)
violet-200: oklch(0.713 0.205 296)
violet-300: oklch(0.560 0.270 295)
violet-400: oklch(0.494 0.275 295)
violet-500: oklch(0.439 0.245 290)
violet-600: oklch(0.366 0.220 287)
violet-700: oklch(0.300 0.197 283)
violet-800: oklch(0.292 0.193 283)
violet-900: oklch(0.279 0.187 282)
color-white: oklch(1.000 0.000 0)
color-black: oklch(0.000 0.000 0)
```
위 OKLCH는 `colors_and_type.css`의 정확한 hex 값을 변환한 결과이며, 원본 hex는 research.md에 기록되어 있다 [src:5].
### Semantic alias (light → dark)
aliases는 raw 토큰 이름만 참조한다 — 값은 base palette에서 유래하므로 별도 색 표기를 두지 않는다.
```yaml
# Background
color-background-canvas: color-white → gray-900
color-background-canvas-200: gray-050 → gray-800
color-background-overlay-100: color-white → gray-800
color-background-primary-100: blue-050 → custom dark
color-background-primary-200: blue-500 → blue-500
color-background-secondary-100: gray-100 → gray-800
color-background-secondary-200: gray-200 → gray-700
color-background-success-100: green-100 → custom dark
color-background-success-200: green-500 → green-500
color-background-warning-100: orange-100 → custom dark
color-background-warning-200: orange-500 → orange-500
color-background-danger-100: red-100 → custom dark
color-background-danger-200: red-500 → red-500
color-background-hint-100: gray-100 → gray-800
color-background-hint-200: gray-600 → gray-600
color-background-contrast-100: gray-300 → gray-700
color-background-contrast-200: gray-800 → gray-100
# Foreground
color-foreground-primary-100: blue-500 → blue-300
color-foreground-primary-200: blue-700 → blue-400
color-foreground-secondary-100: gray-800 → gray-200
color-foreground-secondary-200: gray-900 → gray-100
color-foreground-success-100: green-600 → green-300
color-foreground-success-200: green-700 → green-200
color-foreground-warning-100: orange-600 → orange-300
color-foreground-warning-200: orange-700 → orange-200
color-foreground-danger-100: red-600 → red-300
color-foreground-danger-200: red-700 → red-200
color-foreground-hint-100: gray-600 → gray-400
color-foreground-hint-200: gray-700 → gray-300
color-foreground-contrast-100: gray-800 → gray-200
color-foreground-contrast-200: gray-900 → gray-050
color-foreground-normal-100: gray-700 → gray-300
color-foreground-normal-200: gray-900 → gray-100
# Border
color-border-primary: blue-500 → blue-400
color-border-secondary: gray-200 → gray-700
color-border-success: green-500 → green-400
color-border-warning: orange-500 → orange-400
color-border-danger: red-500 → red-400
color-border-hint: gray-600 → gray-500
color-border-contrast: gray-800 → gray-200
```
다크 모드는 `` 또는 `.vp-dark` 래퍼로 활성화된다 [src:5]. host 앱은 `@vapor-ui/css-generator`로 빌드 단계에서 기본 키 팔레트(예: blue 패밀리)를 자체 브랜드 색으로 교체할 수 있어 — Vapor의 시맨틱 alias는 그대로 둔 채 시각적 1차 색만 swap된다 [src:5].
## Typography
폰트 패밀리는 세 갈래로 분리된다 [src:5]. **Sans (web)**는 Pretendard로 한글과 라틴이 단일 metric을 공유하며 가중치 400/500/600/700/800을 사용한다. **Mono / numerics**는 Inter로 spec 내부의 tabular figure(특히 토큰 표의 OKLCH/HEX 정렬)에 사용된다. **Code blocks**는 Fira Code다. CDN 임포트는 jsdelivr의 Pretendard v1.3.9 + Google Fonts의 Inter, Fira Code 조합이다 [src:5].
토큰화된 사이즈/라인 하이트는 4px 그리드 기반의 13단계 사다리다 [src:5]:
```yaml
# size → fontSize / lineHeight
size-025: 10px / 14px
size-050: 12px / 18px
size-075: 14px / 20px
size-100: 16px / 24px
size-200: 18px / 26px
size-300: 20px / 28px
size-400: 24px / 32px
size-500: 32px / 40px
size-600: 38px / 48px
size-700: 48px / 56px
size-800: 64px / 72px
size-900: 80px / 88px
size-1000: 120px / 130px
```
시맨틱 typography 클래스는 `vapor.css`로 컴파일되어 host 앱에서 직접 클래스 이름으로 호출 가능하다 [src:5]:
| 클래스 | size / line | weight | 비고 |
|---|---|---|---|
| `vp-display1` | 80 / 88 | 800 | letter-spacing -0.4px |
| `vp-display2` | 64 / 72 | 800 | letter-spacing -0.4px |
| `vp-display3` | 48 / 56 | 700 | letter-spacing -0.4px |
| `vp-display4` | 38 / 56 | 700 | letter-spacing -0.4px (의도적 cross-token: fontSize-600 × lineHeight-700) |
| `vp-h1` | 32 / 48 | 700 | 의도적 cross-token: fontSize-500 × lineHeight-600 |
| `vp-h2` | 24 / 32 | 700 | |
| `vp-h3` | 20 / 28 | 700 | |
| `vp-h4` | 18 / 26 | 700 | |
| `vp-h5` | 16 / 24 | 700 | |
| `vp-h6` | 14 / 20 | 700 | |
| `vp-subtitle1` | 14 / 20 | 500 | foreground-hint-100 |
| `vp-subtitle2` | 12 / 18 | 500 | foreground-hint-100 |
| `vp-body1` | 16 / 24 | 400 | |
| `vp-body2` | 14 / 20 | 400 | 본문 기본 |
| `vp-body3` | 12 / 18 | 400 | |
| `vp-body4` | 10 / 14 | 400 | |
| `vp-mono` | — | — | font-mono |
| `vp-num` | — | — | tabular-nums |
Letter-spacing 정책은 기본 `-0.1px`이며 display 1–4는 `-0.4px`로 더 타이트하게 설정된다 — Pretendard에서 한글이 들떠 보이지 않도록 잡는 한글 컨벤션이다 [src:5].
## Spacing
베이스 단위는 4px이다 [src:5]. 토큰 사다리는 0px부터 100px까지 15단계로 정의되며, 컴포넌트 사이징과 border width는 별도 토큰 그룹으로 분리된다 [src:5].
```yaml
size-space-000: 0px
size-space-025: 2px
size-space-050: 4px
size-space-075: 6px
size-space-100: 8px
size-space-150: 12px
size-space-200: 16px
size-space-250: 20px
size-space-300: 24px
size-space-400: 32px
size-space-500: 40px
size-space-600: 48px
size-space-700: 64px
size-space-800: 72px
size-space-900: 100px
# 컴포넌트 높이
size-component-sm: 28px
size-component-md: 32px
size-component-lg: 40px
size-component-xl: 48px
# Border width
size-borderWidth-100: 1px # 보편적 헤어라인
size-borderWidth-200: 2px # focus ring, state border
```
Figma layout 페이지는 outer gutter 100px, 도큐멘테이션 카드의 inner padding 64px를 기본값으로 둔다 [src:5]. Border 정책은 보편적 1px 헤어라인 `color-border-secondary`(light에서 gray-200, dark에서 gray-700)이며, 상태 보더는 매칭되는 colored-500으로 한 단계 강해진다 [src:5].
## Rounded
스케일은 0px부터 40px까지 11단계 + `circle`(9999px) 토큰이다 [src:5]:
```yaml
size-borderRadius-000: 0px
size-borderRadius-050: 2px
size-borderRadius-100: 4px
size-borderRadius-200: 6px
size-borderRadius-300: 8px # 버튼 기본
size-borderRadius-400: 12px # 카드 기본 / 버튼 xl
size-borderRadius-500: 16px
size-borderRadius-600: 20px
size-borderRadius-700: 24px
size-borderRadius-800: 32px
size-borderRadius-900: 40px
size-borderRadius-circle: 9999px # pill / chip / badge / avatar
```
기본 사용 가이드 [src:5]: 카드는 `300` 또는 `400`(8/12px), 버튼은 `300`(8px)이며 size xl만 `400`(12px)으로 한 단계 더 둥글다. pill/badge/avatar는 항상 `circle`(9999px)이다.
## Elevation & Depth
단일 elevation 시스템 4단계이며, 모두 직하 방향 20% 검정으로 통일된다 [src:5]:
```yaml
box-shadow-sm: 0 1px 3px oklch(0 0 0 / .20)
box-shadow-md: 0 2px 10px oklch(0 0 0 / .20)
box-shadow-lg: 0 4px 16px oklch(0 0 0 / .20)
box-shadow-xl: 0 16px 32px oklch(0 0 0 / .20)
```
Inner shadow는 정의되지 않는다 [src:5]. 카드는 그림자 대신 1px 헤어라인 보더에 의존하고, 그림자는 lifted 표면(popover, menu, dialog, toast) 한정으로 사용된다 [src:5]. 다이얼로그와 토스트의 스크림은 `oklch(0 0 0 / .4)`로 고정되며 frosted glass·`backdrop-filter`는 시스템에서 제외된다 [src:5].
모션 토큰은 빠르고 드라마 없는 범위로 설정된다 [src:5]:
```yaml
motion-duration-fast: 120ms
motion-duration-normal: 180ms
motion-duration-slow: 240ms
motion-ease-standard: cubic-bezier(.4, 0, .2, 1)
motion-ease-emphasized: cubic-bezier(.2, 0, 0, 1)
```
상태 변화 정책 [src:5]:
- **Hover** — fill을 한 단계 darker step으로 swap(예: `primary-200 → primary-300`), transform 없음. outline/ghost 스타일은 +8% black overlay.
- **Press / active** — 그 다음 step으로 swap, no scale.
- **Disabled** — opacity 50% 또는 `gray-200`/`gray-500`로 swap, cursor `not-allowed`.
- **Focus-visible** — 2px ring `color-border-primary`, offset 2px.
## Shapes
기하학은 직각형 우위에 보수적인 라운드(2–12px가 일상 사용 범위, `circle`은 pill/badge/avatar 한정)를 둔다. 카드는 흰 표면 위 1px `border-secondary` + 라운드 `400`(12px)이 표준 어휘이며, no-shadow at rest 정책이 카드 표현의 기본값이다 [src:5].
장식의 부재는 의도적이다. 그라디언트·글래스/블러·텍스처·장식 일러스트가 시스템 표면에 일절 등장하지 않으며, 그라디언트는 워드마크와 Getting Started 페이지 컨셉 히어로 두 곳에만 예외적으로 허용된다 [src:5]. 사진 정책은 마케팅 측 goorm 사진의 **clean, daylight, slightly cool, no grain** 톤을 따르되, Vapor 시스템 자체가 강제하는 imagery treatment는 두지 않는다 [src:5].
**Iconography** [src:5] — flat 24×24 monochrome line, 1.5px stroke, React 컴포넌트로 ship된다(``, ``, `` 등). Figma 사용 빈도 1·2위는 `HeartFillIcon`(3120 instances)과 `ChevronDownIcon`(2560 + 2140)이며, fill variant가 outlined 대응으로 존재한다(`Heart` ↔ `HeartFill`). 핸드오프 번들 caveat: Figma 바이너리에서 아이콘이 깨끗이 추출되지 않아 **Lucide via CDN**(24×24 / 1.5px stroke)으로 substitute하며, pixel parity가 필요하면 production SVG를 `assets/icons/`에 드랍한다 [src:5]. 이모지는 제품 UI에서 사용하지 않고 유니코드 글리프를 아이콘 대용으로 쓰지 않는다 — chevron, check, arrow는 모두 SVG다 [src:5].
## Components
`@vapor-ui/core` 1.3.0은 8개 카테고리로 컴포넌트를 노출한다 [src:4][src:5]:
| 카테고리 | 컴포넌트 |
|---|---|
| Layout | Box, Flex, Grid, HStack, VStack |
| Input | Checkbox, Field, InputGroup, MultiSelect, Radio, RadioCard, RadioGroup, Select, SegmentedControl, Switch, TextInput, Textarea |
| Navigation | Breadcrumb, Menu, NavigationMenu, Pagination, Tabs |
| Data Display | Avatar, Badge, Card, Collapsible, Skeleton, Table, Text, Tooltip |
| Feedback | Callout, Dialog, Spinner, Toast |
| Overlay | FloatingBar, Popover, Sheet |
| Button | Button, IconButton |
| Patterns | Form, Form Patterns, Table Patterns, UI Navbar Pattern |
스타일링 레이어는 Base UI(`@base-ui/react`) 프리미티브 위에 Vanilla Extract(`@vanilla-extract/css`, recipes, sprinkles + rainbow-sprinkles)를 얹은 구조이며, React 17/18/19 peer를 모두 지원한다 [src:5]. 모노레포는 6개 패키지로 분할된다 [src:5]:
| 패키지 | 역할 |
|---|---|
| `@vapor-ui/core` | 컴포넌트 라이브러리 본체 |
| `@vapor-ui/hooks` | React 훅 모음 |
| `@vapor-ui/icons` | SVG 아이콘 세트 |
| `@vapor-ui/codemod` | 자동 마이그레이션 도구 |
| `@vapor-ui/color-generator` | WCAG 팔레트 생성기 (Adobe Leonardo 기반) |
| `@vapor-ui/css-generator` | CSS 변수 생성 도구 (host 앱 키 팔레트 swap) |
### button
Vapor Button은 7 variants × 4 sizes를 노출한다 [src:5]. Variants는 functional kind에 해당하므로 각각 `button-*` 엔트리로 분해한다. 사이즈와 상태는 모든 variant에 공통이다.
사이즈는 4단(높이 × 좌우 padding × font-size): `sm` 28×10×13, `md` 32×12×14, `lg` 40×16×15, `xl` 48×20×16. xl만 radius `{rounded.size-borderRadius-400}` (12px)이고 나머지는 `{rounded.size-borderRadius-300}` (8px)이다 [src:5]. disabled는 opacity 0.5 + cursor `not-allowed`, focus-visible은 2px outline `{colors.border-primary}` + offset 2px다 [src:5].
```tsx
import { Button } from "@vapor-ui/core";
// 카노니컬 API — @vapor-ui/core 컴포넌트
// host 앱이 vapor.css 클래스를 직접 쓸 때
```
### button-primary
`vp-btn-primary` / `