Design System

Raw → Semantic → Component

Principles

RAWSEMCMP--size-sm--text-primary--control-h-xs

3-Tier Abstraction

원시 값을 직접 쓰지 않고 Raw → Semantic → Component 세 단계로 감싸기 때문에, 어디를 바꿔도 영향 범위가 그 계층 안에서 통제됩니다. Component 토큰은 button·badge·row 처럼 컴포넌트마다 타입이 정해져 있어서, 엉뚱한 값을 골라 일관성이 깨지는 실수를 막아 줍니다.

Single Source of Truth

토큰 하나를 바꾸면 모든 참조가 함께 바뀝니다. hex 값이 코드베이스에 흩어지지 않습니다.

Semantic 레이어에서 테마 전환

Semantic 변수만 재정의하면 dark/light 전환이 모든 컴포넌트에 자동 반영됩니다.

T

컴포넌트에 raw 값 금지

#hex 나 rgba 를 직접 쓰지 않고 반드시 토큰을 거쳐 참조합니다. 그래서 모든 시각적 결정을 토큰 단위로 되짚어 추적할 수 있습니다.

Token Files

src/styles/tokens/
_color.css브랜드·중립·알파
_typography.css폰트·크기·굵기
_spacing.css--spacing-*
_radius.cssxs → capsule → circle
_shadow.cssxs → 2xl
_motion.cssduration, easing, delay
_z-index.css레이어 순서
_sizing.css컴포넌트 크기
_index.cssbarrel
src/styles/globals/
_semantic.css용도별 의미 매핑

Colors

Presets

Brand

accent
accent-dark
accent-light

Neutral Scale

0
50
100
200
300
400
500
600
700
800
900
950
999

Alpha Variants

Accent Alpha

5%
10%
15%
20%
30%
50%
70%
80%
90%
95%
100%

Neutral Alpha

5%
10%
15%
20%
30%
50%
70%
80%
90%
95%
100%

Semantic Colors

--text-primaryneutral-900
--text-secondaryneutral-800
--text-tertiaryneutral-600
--text-mutedneutral-500
--text-accentaccent
--text-inverseneutral-50
--bg-primaryneutral-50
--bg-inverseneutral-950
--bg-accent-solidaccent

Typography

Variants

h1

Design tokens in action

h2

Design tokens in action

h3

Design tokens in action

h4

Design tokens in action

h5
Design tokens in action
h6
Design tokens in action
body1

Design tokens in action

body2

Design tokens in action

captionDesign tokens in action
overlineDesign tokens in action

Colors

primary
primary color
secondary
secondary color
tertiary
tertiary color
muted
muted color
accent
accent color

Gradient Tokens

--gradient-accent

accent-light → accent-dark

--gradient-accent-soft

accent-light → accent

--gradient-neutral

neutral-300 → neutral-700

Weights

light

normal

medium

semibold

bold

Spacing

zero
0
2xs
0.25rem
xs
0.5rem
sm
0.75rem
md
1rem
lg
1.25rem
xl
1.5rem
2xl
2rem
3xl
3rem
4xl
4rem
5xl
6rem
6xl
8rem

Border Radius

capsule
9999px
알약·칩·행 하이라이트
circle
50%
정원
2xl
24px
면 있는 것
md
8px
예외 전용
sm
6px
예외 전용
xs
4px
예외 전용
2xs
2px
예외 전용

Grid Templates

2 cols · repeat(2, minmax(0, 1fr))
3 cols · repeat(3, minmax(0, 1fr))
4 cols · repeat(4, minmax(0, 1fr))
5 cols · repeat(5, minmax(0, 1fr))
7 cols · repeat(7, minmax(0, 1fr))

Shadows

xs
sm
md
lg
xl
2xl

Motion

Duration

instant
0.1s
fast
0.15s
base
0.3s
moderate
0.35s
slow
0.5s
slower
0.8s
slowest
1.5s

Easing

bounce
cubic-bezier(0.34, 1.56, 0.64, 1)
material
cubic-bezier(0.4, 0, 0.2, 1)
out-expo
cubic-bezier(0.16, 1, 0.3, 1)
in-out
cubic-bezier(0.25, 0.1, 0.25, 1)

Z-Index

Background--z-below = -1
Page Content--z-content = 10
Navigation--z-nav = 100
Floating UI--z-float = 200
Dropdown / Popover--z-dropdown = 500
Tooltip--z-tooltip = 700
Overlay / Drawer--z-overlay = 9000
Cursor / Transition--z-top = 10000

3D (Three.js)

CTA Section

Coffee Cup

컵과 소서는 LatheGeometry, 손잡이는 TorusGeometry, 액면은 CylinderGeometry 로 만듭니다. Canvas 2D 로 parametric heart curve 와 80-band cream↔coffee wave, blur 엽맥 라떼아트 텍스처를 생성해 MeshPhysicalMaterial 에 매핑합니다. 마우스를 따라 lerp 로 부드럽게 회전합니다.

LatheGeometryMeshPhysicalMaterialclearcoatCanvasTextureEnvironment IBL
Hero Section

Scroll Torus

누적 스크롤을 따라 리사주 곡선(X·Y·Z 주파수 차이) 경로를 끝없이 순환하는 메탈릭 토러스입니다. 테마에 따라 색상과 emissive 가 바뀌고, 커서가 가까우면 자석처럼 끌리고 멀면 반발하는 물리 인터랙션이 동작합니다.

TorusGeometryMeshStandardMaterialLissajous pathscroll-drivenpointer repulsion
Profile Section

Bunny Character

몸·귀·팔·발은 LatheGeometry, 머리·눈·꼬리는 SphereGeometry, 찡그린 눈은 CapsuleGeometry 로 조합한 마스코트입니다. 자동으로 눈을 깜빡이고, 클릭하면 놀람·기쁨 표정으로 전환됩니다. RAF 물리 기반으로 벽에 부딪혀 튕기며 이동하고, 충돌 시 사운드가 재생됩니다.

LatheGeometrySphereGeometryCapsuleGeometryexpressionsuseFramephysics

Components

Layout & Brand

Logo
short
full
SectionHeader

제목과 선택적 부가설명, 선택적 우측 액션 슬롯으로 구성됩니다. 여러 곳에 흩어져 있던 <h2>제목</h2> + <p>부가설명</p> 인라인 패턴을 흡수합니다. 저장/되돌리기와 dirty 계산이 결합된 admin 설정의 SectionHeader 와 달리, 이 컴포넌트는 프레젠테이션 전용입니다.

섹션 제목

제목 아래 부가설명이 들어갑니다.

액션 있는 헤더

우측에 버튼 슬롯이 붙습니다.

Collapsible

내용이 maxHeight 를 넘을 때만 접고 더보기를 붙입니다. 넘치지 않으면 버튼도 페이드도 없어서 짧은 내용에는 흔적이 남지 않습니다. 높이는 ResizeObserver 로 추적합니다. 마크다운 이미지는 늦게 로드돼 그때 높이가 바뀌는데, 한 번만 재면 "로드 전 = 안 넘침" 상태로 굳어 긴 댓글이 접히지 않기 때문입니다. 첫 클램프에는 애니메이션을 걸지 않아, 글이 저절로 접히는 듯한 연출을 피합니다.

이 문단은 maxHeight(140px)보다 길어서 접힙니다. 잘린 아래쪽 페이드가 "여기서 끝이 아니다"를 알리는 유일한 시각 단서입니다 — 버튼만 있으면 딱 잘린 글자 줄이 그냥 마지막 줄처럼 읽힙니다.

클램프는 바깥(.clip)이 하고 측정은 안쪽(.inner)이 합니다. 같은 요소가 둘 다 하면 max-height 에 눌린 높이를 재게 되어 항상 "딱 맞음"이 나옵니다.

펼친 뒤 높이는 실제로 auto 입니다 — framer-motion 이 height: "auto" 를 실측해 애니메이트하기 때문에, 나중에 이미지가 더 로드돼 내용이 길어져도 잘리지 않습니다.

chevron 회전 transition 은 .root .chevron 처럼 compound 셀렉터로 씁니다. 전역 theme transition(html[data-theme-ready] *)이 shorthand 라 단일 클래스로는 transform transition 이 통째로 덮어써집니다.

Buttons & Actions

Button
Variants
Sizes
Shapes
Icons & States
Tones

tone 은 variant 위에 의미를 나타내는 색을 얹습니다. 모든 조합이 정의돼 있지는 않고 실제로 쓰이는 조합만 존재합니다. accent 와 danger 는 둘 다 ghost 에서 색이 시작하지만 hover 방향은 반대입니다. accent 는 text-primary 로 가라앉고(강조 → 평상), danger 는 text-error 로 올라옵니다(평상 → 경고).

Pressable

눌리는 것의 동작만 담는 기반입니다 — type="button" · 클릭/호버 사운드 · disabled · 누를 때 축소. 생김새는 주지 않으므로 className 으로 쓰는 쪽이 정합니다. MUI 의 ButtonBase, React Aria 의 useButton 과 같은 구조입니다. 버튼처럼 생긴 것은 Button 을, 절대위치로 깔린 클릭 영역·부모 글꼴을 물려받는 페이지 번호·::after 로 밑줄을 그리는 칩처럼 생김새를 통일하면 안 되는 자리Pressable 을 씁니다. raw <button> 은 쓰지 않습니다 — 사운드가 빠지고 type 누락 시 폼이 제출됩니다.

HelpButton

도움말 ? 버튼으로, Button 의 subtle/circle 을 고정한 wrapper 입니다. variant·shape·children 은 일관성을 위해 고정하고 size 만 열어 둡니다(폼 라벨 옆에는 2xs, 섹션 헤더에는 sm 을 씁니다). 나머지 props 는 Button 으로 그대로 흘려보내므로 Popover/Tooltip 의 trigger 로 바로 쓸 수 있습니다.

2xs
xs
sm
md
lg
xl
+ Popover
SpinButton

누르고 있으면 action 을 가속하며 반복합니다. 클릭하면 즉시 한 번 실행하고, 380ms 이상 누르고 있으면 반복을 시작합니다(130ms 에서 28ms 로 점점 빨라집니다). 버튼 밖에서 손을 떼도 pointerCapture 로 안전하게 멈춥니다. 자체 시각 스타일은 없고, 놓이는 자리의 모양을 className 으로 받습니다(NumberInput 의 스텝퍼가 그 예입니다).

0꾹 눌러보세요
CloseButton

닫기 버튼입니다. 평소엔 minus(하단 line 하나)였다가 hover 하면 두 line 이 X 로 morph 합니다([data-active] 로 강제 X 상태도 가능). vector line 을 top: 50%; left: 50% + negative margin 으로 sub-pixel 정렬해 어느 크기에서도 정확히 겹칩니다. 아래 버튼에 마우스를 올려 보세요.

xs · 20px
sm · 24px
md · 32px
lg · 38px
MenuDots

메뉴 버튼 아이콘입니다. 9-dot 격자가 눌리면 X 로 모였다가 닫힐 때 다시 흩어집니다. 사이트 Navigation 메뉴 버튼과 admin/settings 서랍 토글이 같은 아이콘을 쓰도록 공용화했습니다. viewBox 8×8 의 vector circle 이라 어떤 크기에서도 완전한 원을 유지합니다(작은 span 에서는 subpixel 안티앨리어싱이 dot 마다 달라 타원처럼 보였습니다). 클릭해 보세요.

Toggles & Selection

SortControl

정렬 필드 Select 와 역순 토글을 하나의 pill 로 결합합니다. 달력 블록 툴바와 댓글이 함께 씁니다. 예전에는 달력은 pill, 댓글은 gap 배치에 고정 아이콘이라 같은 기능이 서로 다르게 보였습니다. 역순 아이콘은 현재 정렬 방향을 그대로 반영합니다(고정 아이콘은 '누르면 뒤집힌다'만 알려줄 뿐 지금 방향은 알려주지 못합니다).

SegmentedControl

캡슐 안에서 하나를 고르는 컨트롤로, posts 정렬·시리즈/태그 필터·달력 뷰 전환 등 33곳에서 씁니다. 테두리를 border 가 아니라 inset box-shadow 로 그립니다. border 는 layout 에 영향을 줘서 '전체 높이 = 버튼 높이 + padding' 규칙을 지킬 수 없기 때문입니다(그래서 색만 바꾸려 해도 border-color 로는 먹지 않습니다). variant 는 테두리 세기만 가릅니다. subtle 은 주변이 전부 border-light 결인 자리(달력 블록)에서 기본값이 혼자 진하게 튀는 것을 막아 줍니다.

default
subtle
Checkbox
Switch
Default (sm)
Accent
Disabled
Disabled On
With label
showStateText (ON/OFF)
LanguageToggle
KOKOENEN
Default (md, 28px)
KOKOENEN
size="sm" (22px)

Inputs & Fields

Input
Variants
EN
Sizes & States
Slider
Single — 40
Range — 20~80
Disabled
NumberInput

스텝퍼는 SpinButton 이라 누르고 있으면 가속하며 반복합니다. 경계값에 닿으면 toast 로 알리되, hold-repeat 로 도배되는 것을 막으려고 1.2초당 한 번만 띄웁니다.

Basic — 50 · 스텝퍼 + blur/Enter 확정
Label + unit — 320pxWpx
Gauge — 50% (0~100 중 위치에 따라 숫자 색)%
Textarea
maxHint (contenteditable highlight)

maxHint 설정 시 contenteditable="plaintext-only" 모드로 자동 전환 — 초과 글자에 inline <mark> highlight · preset (short 200 / basic 500 / long 2000) 또는 숫자 · 카운터 80% 부터 warning, 100% 부터 over · native resize 핸들 위 투명 overlay 로 커스텀 cursor 표시

tabIndent

tabIndent 는 opt-in 입니다. 키보드로 폼을 빠져나갈 수 있으려면 기본적으로 Tab 이 다음 포커스로 동작해야 하기 때문입니다(a11y). 그래서 코드나 마크다운을 입력하는 칸(댓글 작성란 등)에서만 켭니다. maxHint 가 있어야 켜지는 contenteditable 모드 전용입니다.

0 / 200
SearchCapsule

접힌 아이콘에서 펼쳐지는 검색 인풋입니다. 지우개·검색 이력·문법 도움말을 자체 내장해서, 목록이나 필터 바에서 raw input 을 다시 짤 필요가 없습니다.

typeSelector · historyKey · showHelp

실제 필터 바는 이 옵션들을 한꺼번에 씁니다. 왼쪽 typeSelector 로 검색 범위를, 오른쪽 ?(showHelp) 말풍선으로 문법·매칭 강도(prefix/regex) 도움말을 열고, historyKey 로 최근 검색을 저장합니다(검색어 입력 → Enter → 빈 상태로 다시 포커스하면 이력이 뜹니다).

collapsible

collapsible 은 접힌 원형 아이콘에서 클릭 시 캡슐로 펼쳐지고, 비었을 때 blur 하면 다시 접힙니다. compact 배치용 — 펼침 너비는 expandedWidth 로 조정합니다.

size

size 는 캡슐 높이를 정합니다 — sm(28) · md(32, 기본). align 은 dropdown 정렬을 좌/우로 잡습니다.

HighlightInput

native <input> 은 텍스트 일부만 색칠할 수 없어서, 초과 글자에 inline <mark> 하이라이트를 하려면 contentEditable 이 필요합니다 — 그걸 위한 단일행 contentEditable input 입니다(native 가 공짜로 주는 caret·IME·autofill 을 대신 손으로 재구현하는 대신 하이라이트를 얻는 트레이드오프). 하이라이트가 필요할 때만 이걸 직접 쓰고, 그 외엔 native Input 을 씁니다 — 둘은 완전히 분리돼 있고 서로를 모릅니다. 제한은 두 갈래 — maxHint권장 한도라 초과분에 <mark> 와 카운터만 띄우고 자르진 않으며(붙여넣은 긴 제목 보존), maxLength하드 상한이라 입력·붙여넣기 시점에 잘라냅니다. inlineLabel 로 KO/EN 배지를 input 안에 넣습니다.

KO
17 / 20

Pickers & Selects

Select
Variants
States
Combobox

combobox 는 input 을 trigger 로 씁니다. label 과 searchTerms(한글 alias)로 필터하고, Enter 나 쉼표로 free text 를 추가하며, 지우개 버튼과 화살표 키 이동, group/icon 옵션을 지원합니다.

React
editable — 프리셋 밖 값 직접 입력

editable 을 주면 트리거를 더블클릭할 때 입력칸으로 바뀌어, 드롭다운 프리셋에 없는 값도 직접 타이핑할 수 있습니다(에디터 툴바의 폰트 크기·줄간격 입력이 이 방식입니다). 한 번 클릭은 평소대로 드롭다운을 엽니다 — 더블클릭과 구분하려고 첫 클릭을 250ms 지연시킵니다. editableInputPropsmaxLength(길이 제한)·placeholder·sanitize(확정 직전 정규화, 예: 숫자만 · 숫자·점만)를 지정합니다. blur 나 Enter 로 확정, Escape 로 취소하며, 프리셋에 없는 값은 현재 값을 임시 옵션으로 얹어 드롭다운에서도 보이게 합니다. value 가 비어 있으면 처음부터 입력 모드로 시작합니다(PeriodPicker 의 연·월·일 칸이 그 예입니다).

Font size — 16px · 더블클릭해 직접 입력
Line height — 1.6 · 더블클릭해 직접 입력
ColorPicker

render-prop trigger 와 portal popover 로 이뤄집니다. trigger 를 호출부가 직접 그리기 때문에 스와치·버튼·칩 등 무엇이든 될 수 있습니다. 모바일(≤768px)에서는 dropdown 대신 bottom sheet 으로 바뀝니다. 색을 고르려면 두 손가락만 한 면적이 필요한데, 작은 화면의 popover 로는 그만한 공간이 나오지 않기 때문입니다. inline 모드는 이미 제자리에 펼쳐진 형태라 sheet 전환에서 제외됩니다.

#d01046
DatePicker
Format
2024.03.15
Spinner
Calendar
PeriodPicker
표시 형식
미리보기2024.03 - 2024.12
시작
종료
EmojiPicker

이모지·아이콘·커스텀 이미지를 선택합니다. 탭 전환과 검색, 셔플, 최근 사용 목록을 제공합니다.

FontPicker

폰트를 고르는 드롭다운입니다. 각 항목이 그 폰트 그대로 렌더되기 때문에, 고르기 전에 실제 생김새를 미리 볼 수 있습니다. 폰트를 groups 로 묶어 넘기면 그룹마다 라벨(예: 영문·고정폭)이 붙고, 그룹이 하나뿐일 때는 group: "" 로 그 라벨만 숨길 수 있습니다. 어떤 항목에 googleName 을 지정해 두면 그 폰트를 고르는 순간 컴포넌트가 해당 Google Font 를 알아서 불러오므로, 호출부에서 따로 로드하지 않아도 됩니다. preferEn 을 켜면 한글 그룹이 목록 맨 뒤로 밀려서 영문 UI 를 우선하는 자리에 맞출 수 있습니다.

다람쥐 헌 쳇바퀴에 타고파 · The quick brown fox 0123

Chips

Chip
draggable capsule
ReactNext.jsTypeScriptGSAP
variants & states

variant 는 capsule 또는 bare 를 고르고, leftIcon·active(편집 중)·onClick(button) 을 지원합니다. 핸들 위에 커서를 올리면 data-cursor 로 "Drag" 커서가 나타납니다.

react#tagediting…draggable
status dot (발행 상태)

variant="capsule"leftIcon 으로 색 dot 을 넣어 상태를 구분합니다. 에디터 상단 바의 발행 상태 표시가 이 패턴입니다 — 발행됨은 success, 예약 발행은 warning, 미발행은 muted 색 dot 입니다. dot 은 7px 원이고 색만 semantic 토큰으로 바뀝니다.

발행됨예약 발행미발행
RelatedChips

관련 글이나 프로젝트를 보여 주는 칩입니다. 썸네일·제목·카테고리를 담고, +N 더보기 토글을 제공하며, 데스크톱에서는 hover 시 미리보기 카드를 띄웁니다.

Overlays & Popovers

Modal
ImageViewer

아래 이미지를 클릭하면 뷰어가 열립니다. 확대·이동·썸네일 탐색·풀스크린을 지원합니다.

Popover
base

anchor + portal · outside click / ESC 자동 닫힘 · 터치 디바이스에선 bottom sheet 로 자동 분기 · 모달 안에선 그 stacking context 로 portal 돼 전역 z-index 없이도 모달 위에 뜬다

variants

예전에는 호출부마다 유리 배경을 제각각 override 했는데, 이를 variant 로 흡수했습니다. glass(기본)는 반투명 scrim 과 blur 라 안쪽 색이 그대로 살아납니다. solid 는 뒤가 전혀 비치면 안 될 때 씁니다. difference 는 패널째 뒤 페이지와 반전 합성해서 밑에 무엇이 깔리든 대비가 자동으로 잡힙니다. 다만 difference 는 subtree 가 한 덩어리로 합성돼 안쪽 색 구분이 사라지고, 배경이 임의의 색이면 색이 틀어집니다. 그래서 기본값은 glass 입니다. 아래 그라데이션 위에 열어 비교해 보세요.

openOnHover

데스크톱에서는 hover 로 열립니다. 열림은 즉시 이뤄지고 닫힘은 500ms 지연되므로, trigger 와 content 사이를 지나가도 닫히지 않습니다. hover popover 는 한 번에 하나만 열립니다(둘을 번갈아 올려 보세요). 클릭도 그대로 동작하며, hover 개념이 없는 터치/sheet 모드에서는 무시됩니다.

Feedback & Display

Toast

스택 중 하나에 hover 하면 그 토스트만이 아니라 전체가 멈춥니다(pauseAllToasts). 하나씩만 멈추면 다른 토스트가 사라지면서 스택이 재배치되고, 커서가 저절로 벗어나기 때문입니다. 클릭하면 즉시 사라집니다.

Pagination
/ 12
TypeWriter
— Design System
HeartIcon

state machine (empty / filling / filled / draining) · 3-layer wave fill (SVG path d 애니메이션) · 채우기 완료 시 burst 1회 · burst 거리/크기 size 비례 스케일 · stroke / fill 모두 currentColor 상속 (부모 color 따라감)

size 14size 20size 32
HighlightedText

검색어와 일치하는 부분만 <mark> 로 감쌉니다. query 를 주지 않으면 SearchHighlightProvider context 의 값을 쓰므로, 리스트의 각 행이 검색어를 일일이 넘겨받을 필요가 없습니다. query 가 비면 그냥 평문으로 렌더되므로, 조건 분기 없이 항상 이 컴포넌트를 쓰면 됩니다.

검색어가 들어간 문장입니다
query 없음 → 평문: 강조 없음
Inline Color Swatch

인라인 code 의 내용이 색상값(#hex · rgb() · hsl())이면 앞에 색 원(스와치)이 붙습니다 — GitHub 스타일. 렌더 후 applyColorSwatches 가 인라인 코드를 스캔해 검증된 색만 배경으로 주입하므로, 이름색·비색상은 평문으로 남습니다. 인라인 코드 자체는 Notion 식 배경형(보더 없음)입니다. 에디터에서는 툴바의 색상 칩 도구(Palette 아이콘)로 팔레트에서 고른 #hex 를 인라인 코드로 삽입하고, 리더가 이걸 그대로 이 스와치로 렌더합니다.

#e11d48rgb(46, 204, 113)hsl(280, 70%, 55%)not-a-color
Code Block Controls

리더/미리보기에서 코드블록 위에 붙는 바 — 좌측 언어 라벨 + 우측 복사·줄바꿈 토글. 두 버튼은 각자 떠오른 emboss 타일로 구분되고, 코드블록 위 세로 스크롤은 페이지로 통과합니다(축 기반 wheel 라우팅). 런타임엔 attachCodeWrapToggle 이 주입합니다.

css

Tooltip

Basic
JSX content
Translation Tooltip — <T>

마우스를 올리면 반대 언어 번역을 보여 줍니다. (지연: 0ms / 600ms)

연락하기
이메일 보내기
전송 완료!
기억은 흐려지지만, 기록은 남고, 나눌수록 깊어진다.