소프트웨어 엔지니어를 위한 디자인 원칙
더 나은 소프트웨어 구축을 위한 디자인 기초를 배우세요. 타이포그래피, 색채 이론, 간격, 애니메이션, Arc부터 Stripe까지 16개 사례 연구.
2026년 2월 9일 업데이트
2026년 2월 업데이트: 두 개의 새로운 섹션을 추가했습니다—인터랙션 패턴(Framer, Flighty, Halide, Warp, Bear, Craft, Superhuman을 분석하여 정리한 8가지 패러다임)과 AI 인터페이스 패턴(Perplexity에서 도출한 인용 중심 디자인, 스트리밍 단계, 오류 투명성). 웹 패턴을 2026년 기준으로 업데이트하여 앵커 포지셔닝, 스크롤 기반 애니메이션, @starting-style을 반영했습니다. 접근성 항목은 WCAG 2.2의 ISO 표준화를 반영하여 갱신했습니다. 16개 우수 제품에 대한 심층 분석은 디자인 스터디를 참고하세요.
저는 소프트웨어를 만들면서 수년간 디자인을 연구해 왔습니다. Dieter Rams 같은 거장들의 원칙을 흡수하고, Linear, Stripe, Raycast 같은 제품의 인터페이스를 분석했습니다. 이 가이드는 제가 소프트웨어의 외관과 사용감에 관심을 갖기 시작했을 때 있었으면 했던 종합 레퍼런스로, 그간의 이해를 응축한 결과물입니다.
디자인은 장식이 아닙니다. 커뮤니케이션입니다. 모든 픽셀은 기능, 계층 구조, 의미를 전달합니다. 아마추어처럼 느껴지는 소프트웨어와 프로페셔널하게 느껴지는 소프트웨어의 차이는 이러한 원칙을 이해하고 일관되게 적용하는 데 있습니다.
이 가이드는 이미 코드를 작성할 수 있는 분을 대상으로 합니다. 여기서 배우는 것은 ‘보는 눈’입니다—어떤 인터페이스는 왜 자연스럽게 느껴지고 다른 인터페이스는 왜 혼란스럽게 느껴지는지 이해하는 것, 그리고 더 중요하게는 전자를 직접 만드는 방법을 익히는 것입니다.
목차
파트 1: 기초
파트 2: 인터랙션과 AI
파트 3: 디자인 철학
파트 4: 구현
파트 5: 참고 자료
Gestalt 심리학
“전체는 부분의 합과는 다른 것이다.” — Kurt Koffka
Gestalt 심리학은 1920년대 독일에서 발전한 이론으로, 인간이 시각 정보를 어떻게 인지하는지를 설명합니다. 뇌는 개별 픽셀을 따로 보지 않고, 요소들을 의미 있는 패턴으로 조직화합니다. 이 원칙들을 숙달하면 사용자가 인터페이스를 어떻게 인식할지 제어할 수 있습니다.
근접성 (Proximity)
서로 가까이 있는 요소들은 하나의 그룹으로 인식됩니다.
이것은 UI 디자인에서 가장 강력한 Gestalt 원칙입니다. 여백은 다른 어떤 시각적 속성보다 요소 간의 관계를 효과적으로 전달합니다.
WRONG (equal spacing = no grouping):
┌─────────────────┐
│ Label │
│ │
│ Input Field │
│ │
│ Label │
│ │
│ Input Field │
└─────────────────┘
RIGHT (unequal spacing = clear groups):
┌─────────────────┐
│ Label │
│ Input Field │ ← Tight (4px) - related
│ │
│ │ ← Wide (24px) - separating groups
│ Label │
│ Input Field │ ← Tight (4px) - related
└─────────────────┘
CSS 구현:
.form-group {
margin-bottom: 24px; /* Between groups: wide */
}
.form-group label {
margin-bottom: 4px; /* Label to input: tight */
display: block;
}
SwiftUI 구현:
VStack(alignment: .leading, spacing: 4) { // Tight within group
Text("Email")
.font(.caption)
.foregroundStyle(.secondary)
TextField("[email protected]", text: $email)
.textFieldStyle(.roundedBorder)
}
.padding(.bottom, 24) // Wide between groups
유사성 (Similarity)
시각적 특성을 공유하는 요소들은 서로 관련된 것으로 인식됩니다.
요소들이 같은 모습을 가지면, 사용자는 그것들이 같은 기능을 한다고 가정합니다. 이것이 바로 디자인 시스템에서 일관된 버튼 스타일, 카드 처리, 타이포그래피를 사용하는 이유입니다.
Example Navigation:
┌───────────────────────────────────┐
│ [Dashboard] [Projects] [Settings] │ ← Same style = same function
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Card │ │Card │ │Card │ │ ← Same style = same content type
│ └─────┘ └─────┘ └─────┘ │
│ │
│ [+ New Project] │ ← Different style = different function
└───────────────────────────────────┘
전경-배경 (Figure-Ground)
콘텐츠는 배경과 명확히 분리되어야 합니다.
뇌는 “전경”(집중할 대상)과 “배경”(뒤쪽 면)을 구분해야 합니다. 전경-배경 관계가 불분명하면 시각적 혼란이 발생합니다.
기법: - 대비 (밝은 전경에 어두운 배경, 또는 그 반대) - 그림자 (전경을 배경 위로 띄우기) - 테두리 (전경의 경계 표시) - 블러 (배경을 흐리게, 전경은 선명하게)
/* Strong figure-ground relationship */
.card {
background: var(--color-surface); /* Figure */
border-radius: 12px;
box-shadow: 0 1px 3px rgba(0,0,0,0.1); /* Elevation */
}
.modal-overlay {
background: rgba(0, 0, 0, 0.5); /* Dim ground */
backdrop-filter: blur(4px); /* Blur ground */
}
공통 영역 (Common Region)
경계 안에 있는 요소들은 하나의 그룹으로 인식됩니다.
요소들을 시각적 컨테이너(카드, 박스, 테두리 영역)로 감싸면 해당 요소들이 함께 속한다는 신호를 보냅니다.
연속성 (Continuity)
시선은 경로, 선, 곡선을 따라 흐릅니다.
정렬과 시각적 흐름을 활용하여 인터페이스 전체에 걸쳐 시선을 유도하세요.
CONTINUITY IN ALIGNMENT:
┌────────────────────────────────┐
│ Logo [Nav] [Nav] [Nav] │ ← Aligned on horizontal axis
├────────────────────────────────┤
│ │
│ Headline │
│ ───────────────────────────── │ ← Eye follows left edge
│ Paragraph text continues │
│ along the same left edge │
│ │
│ [Primary Action] │ ← Still on the left edge
└────────────────────────────────┘
폐합 (Closure)
뇌는 불완전한 형태를 완성합니다.
사용자는 모든 픽셀이 그려질 필요가 없습니다—익숙한 형태는 머릿속에서 자연스럽게 완성합니다. 이를 통해 더 간결하고 세련된 디자인이 가능해집니다.
/* Horizontal scroll with partial card (closure) */
.card-carousel {
display: flex;
gap: 16px;
overflow-x: auto;
padding-right: 48px; /* Show partial card = scroll hint */
}
.card-carousel .card {
flex: 0 0 280px; /* Fixed width, partial visible */
}
Gestalt 빠른 참조
| 원칙 | 규칙 | 주요 용도 |
|---|---|---|
| 근접성 | 관련 있는 것은 가깝게, 관련 없는 것은 멀리 | 폼 필드, 콘텐츠 섹션 |
| 유사성 | 같은 모습 = 같은 기능 | 버튼, 카드, 내비게이션 |
| 전경-배경 | 레이어의 명확한 분리 | 카드, 모달, 오버레이 |
| 공통 영역 | 경계가 콘텐츠를 그룹화 | 설정 섹션, 사용자 카드 |
| 연속성 | 선과 정렬을 따라 흐름 | 타임라인, 읽기 흐름 |
| 폐합 | 뇌가 형태를 완성 | 아이콘, 스크롤 힌트, 스켈레톤 |
타이포그래피
“타이포그래피는 인간의 언어에 지속적인 시각적 형태를 부여하는 기술이다.” — Robert Bringhurst
타이포그래피는 인터페이스 디자인의 토대입니다. 텍스트는 기능, 위계, 브랜드를 전달합니다. 부실한 타이포그래피는 인터페이스를 사용하기 어렵게 만들고, 훌륭한 타이포그래피는 보이지 않습니다—그저 자연스럽게 작동합니다.
타입 스케일
일관된 스케일은 시각적 조화를 만듭니다. 수학적 비율을 사용하세요.
1.25 스케일 (UI에 권장):
:root {
/* Base: 16px (1rem) */
--text-xs: 0.64rem; /* 10.24px - use sparingly */
--text-sm: 0.8rem; /* 12.8px - captions, labels */
--text-base: 1rem; /* 16px - body text */
--text-lg: 1.25rem; /* 20px - lead text */
--text-xl: 1.563rem; /* 25px - h4 */
--text-2xl: 1.953rem; /* 31.25px - h3 */
--text-3xl: 2.441rem; /* 39px - h2 */
--text-4xl: 3.052rem; /* 48.8px - h1 */
}
행간 (Line Height)
행간은 가독성에 큰 영향을 미칩니다. 콘텐츠 유형에 따라 다른 행간이 필요합니다.
| 콘텐츠 유형 | 행간 | 이유 |
|---|---|---|
| 제목 | 1.1 - 1.2 | 촘촘하고, 굵고, 짧음 |
| UI 텍스트 | 1.3 - 1.4 | 레이블, 버튼 |
| 본문 텍스트 | 1.5 - 1.7 | 읽기 편한 문단 |
| 장문 콘텐츠 | 1.7 - 2.0 | 아티클, 문서 |
줄 길이 (Measure)
최적의 줄 길이는 눈의 피로를 방지하고 독해력을 향상시킵니다.
- 최적: 한 줄당 45-75자
- 목표: 50-65자
- 절대 최대: 85자
p {
max-width: 65ch; /* ch unit = width of '0' character */
}
.article-body {
max-width: 70ch;
margin: 0 auto;
}
폰트 선택
시스템 폰트를 우선 사용하세요. 즉시 로드되고, 플랫폼과 일치하며, 화면에 최적화되어 있습니다.
:root {
--font-sans: system-ui, -apple-system, BlinkMacSystemFont,
'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
--font-mono: ui-monospace, 'SF Mono', 'Cascadia Code',
'JetBrains Mono', Consolas, monospace;
}
커스텀 폰트를 사용할 때: - 브랜드 차별화 (마케팅 사이트) - 에디토리얼/출판 느낌 - 시스템 폰트로 달성할 수 없는 특정 디자인 의도
폰트 굵기로 위계 표현
크기뿐만 아니라 굵기를 활용하여 위계를 설정하세요.
h1 { font-weight: 700; } /* Bold */
h2 { font-weight: 600; } /* Semibold */
h3 { font-weight: 600; } /* Semibold */
.lead { font-weight: 500; } /* Medium */
p { font-weight: 400; } /* Regular */
.meta { font-weight: 400; color: var(--text-muted); }
타이포그래피 빠른 참조
| 속성 | 본문 텍스트 | 제목 | UI 레이블 |
|---|---|---|---|
| 크기 | 16-18px | 24-48px | 12-14px |
| 굵기 | 400 | 600-700 | 500 |
| 행간 | 1.5-1.7 | 1.1-1.2 | 1.3-1.4 |
| 줄 길이 | 45-75ch | 해당 없음 | 해당 없음 |
| 정렬 | 왼쪽 | 가운데 가능 | 왼쪽 |
색채 이론
“색채는 영혼에 직접적으로 영향을 미치는 힘이다.” — Wassily Kandinsky
색채는 언어보다 빠르게 소통합니다. 분위기를 조성하고, 시선을 유도하며, 의미를 전달하고, 브랜드 인지도를 구축합니다.
60-30-10 규칙
균형 잡힌 인터페이스를 위한 가장 신뢰할 수 있는 색상 배분법입니다.
┌──────────────────────────────────────────┐
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│ 60% - Dominant (Background)
│░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░│
│░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░│ 30% - Secondary (Cards, sections)
│░░░░░▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓▓░░░░░░░░│
│░░░░░▓▓▓▓▓▓▓▓▓▓▓▓██████▓▓▓▓▓▓▓▓▓▓░░░░░░░░│ 10% - Accent (Buttons, links)
└──────────────────────────────────────────┘
색상 팔레트 구축
모든 인터페이스에는 다음과 같은 시맨틱 색상이 필요합니다:
:root {
/* Brand */
--color-primary: hsl(220, 80%, 50%);
--color-primary-hover: hsl(220, 80%, 45%);
/* Semantic */
--color-success: hsl(142, 76%, 36%); /* Green - positive */
--color-warning: hsl(38, 92%, 50%); /* Amber - caution */
--color-error: hsl(0, 84%, 60%); /* Red - danger */
/* Neutrals */
--color-background: hsl(0, 0%, 100%);
--color-surface: hsl(220, 14%, 96%);
--color-border: hsl(220, 13%, 91%);
/* Text */
--color-text: hsl(220, 13%, 13%);
--color-text-secondary: hsl(220, 9%, 46%);
--color-text-muted: hsl(220, 9%, 64%);
}
색채 심리학
| 색상 | 심리적 효과 | UI 활용 |
|---|---|---|
| 파란색 | 신뢰, 안정, 평온 | 금융, 기술, 기업 |
| 초록색 | 성장, 자연, 성공 | 건강, 친환경, 긍정적 상태 |
| 빨간색 | 에너지, 긴급, 위험 | 알림, 세일, 오류 |
| 주황색 | 따뜻함, 열정 | CTA, 활기찬 브랜드 |
| 노란색 | 낙관, 주의 | 경고, 하이라이트 |
| 보라색 | 고급, 창의성 | 프리미엄 제품 |
다크 모드 우선 디자인 (Vercel)
Vercel은 다크 모드를 먼저 디자인한 다음 라이트 모드를 파생합니다. 이 방식은 다크 모드가 부차적인 고려가 아닌 주요 고려 대상이 되기 때문에 더 나은 다크 인터페이스를 만들어냅니다.
/* Design dark first, derive light */
:root {
/* Dark mode defaults */
--color-background: hsl(0, 0%, 0%);
--color-surface: hsl(0, 0%, 7%);
--color-border: hsl(0, 0%, 15%);
--color-text: hsl(0, 0%, 93%);
--color-text-secondary: hsl(0, 0%, 63%);
}
@media (prefers-color-scheme: light) {
:root {
--color-background: hsl(0, 0%, 100%);
--color-surface: hsl(0, 0%, 97%);
--color-border: hsl(0, 0%, 89%);
--color-text: hsl(0, 0%, 9%);
--color-text-secondary: hsl(0, 0%, 40%);
}
}
적합한 경우: 개발자 도구, 미디어 앱, 대시보드—사용자가 장시간 사용하며 다크 모드가 눈의 피로를 줄여주는 환경.
접근성 명도 대비
| 수준 | 일반 텍스트 | 큰 텍스트 | UI 컴포넌트 |
|---|---|---|---|
| AA | 4.5:1 | 3:1 | 3:1 |
| AAA | 7:1 | 4.5:1 | 해당 없음 |
WCAG 2.2는 2025년 10월 ISO 표준(ISO/IEC 40500:2025)이 되었으며, 포커스 가시성, 중복 입력, 접근 가능한 인증에 대한 기준이 추가되었습니다. 주요 추가 사항: 포커스 표시기가 다른 콘텐츠에 의해 완전히 가려지면 안 되며(2.4.11), 인증이 인지 기능 테스트에만 의존해서는 안 됩니다(3.3.8).
도구: WebAIM Contrast Checker, Chrome DevTools 색상 선택기
시각적 계층 구조
“디자인은 브랜드의 조용한 대사입니다.” — Paul Rand
시각적 계층 구조는 사용자가 무엇을 먼저, 두 번째로, 세 번째로 보는지를 결정합니다. 명확한 계층 구조가 없으면 사용자는 정보를 찾기 위해 노력해야 합니다. 계층 구조가 잘 잡혀 있으면 인터페이스가 자연스럽게 느껴집니다.
계층 구조의 여섯 가지 도구
1. 크기 — 큰 요소가 먼저 시선을 끕니다
.hero-title { font-size: 3rem; } /* Dominant */
.section-title { font-size: 1.5rem; } /* Secondary */
.body-text { font-size: 1rem; } /* Baseline */
2. 굵기 — 굵은 글씨는 앞으로 튀어나오고, 가벼운 글씨는 뒤로 물러납니다
h1 { font-weight: 700; }
.lead { font-weight: 500; }
p { font-weight: 400; }
3. 색상과 대비 — 높은 대비 = 주목
.title { color: var(--color-text); } /* Near black */
.meta { color: var(--color-text-muted); } /* Gray */
4. 위치 — 핵심 위치가 중요합니다
F-PATTERN (콘텐츠 페이지): Z-PATTERN (랜딩 페이지):
████████████████████████ 1 ──────────────────► 2
████████ ↘
████ ↘
██ ↘
3 ──────────────────► 4
5. 여백 — 고립이 중요성을 만듭니다
.hero { padding: 120px 48px; } /* Generous space */
.data-table { padding: 12px; } /* Dense content */
6. 깊이와 높이감 — 앞으로 튀어나오는 요소가 시선을 끕니다
:root {
--shadow-sm: 0 1px 2px rgba(0,0,0,0.05);
--shadow-md: 0 4px 6px rgba(0,0,0,0.1);
--shadow-lg: 0 10px 15px rgba(0,0,0,0.1);
}
.card { box-shadow: var(--shadow-sm); }
.card:hover { box-shadow: var(--shadow-md); }
.modal { box-shadow: var(--shadow-lg); }
적용 패턴
협업 프레즌스 (Figma): 여러 사용자의 커서가 이름 라벨, 선택 하이라이트, 컴포넌트 외곽선과 함께 표시되어 살아있는 문서를 만들어냅니다. 각 협업자의 색상은 구분되지만 동일한 비중을 가지며, 어떤 커서도 다른 커서보다 “더 크게” 보이지 않습니다.
은은한 상태 표시기 (Vercel): 배포 상태는 방해가 되는 알림 대신 미묘하고 지속적인 표시기를 사용합니다. 상단의 얇은 색상 바가 워크플로를 중단하지 않으면서 상태(빌드 중, 배포 완료, 실패)를 전달합니다.
실세계 디자인 비유 (Flighty): 비행 진행 시각화는 물리적 항공 계기를 모방합니다. 고도 곡선, 속도 표시기, 게이트 맵이 추상적인 진행률 바 대신 익숙한 시각적 메타포를 사용합니다.
눈 가늘게 뜨기 테스트
디자인을 눈을 가늘게 뜨고 바라보세요. 여전히 계층 구조가 보이나요? 보인다면 강한 계층 구조입니다.
간격과 리듬
“여백은 공기와 같습니다. 디자인이 숨을 쉬기 위해 꼭 필요합니다.” — Wojciech Zieliński
간격은 디자인의 보이지 않는 구조입니다. 일관된 간격은 시각적 리듬을 만들어 요소들이 하나의 일관된 시스템에 속해 있다는 느낌을 줍니다.
8px 그리드
8px 그리드가 업계 표준인 이유는 다음과 같습니다: - 균등하게 나뉩니다 (8, 16, 24, 32, 40, 48…) - 일반적인 화면 밀도에서 잘 작동합니다 (1x, 1.5x, 2x, 3x) - 계산 없이 일관된 리듬을 만들어냅니다
:root {
--space-1: 4px; /* Tight: icon gaps */
--space-2: 8px; /* Compact: inline elements */
--space-3: 12px; /* Snug: form fields */
--space-4: 16px; /* Default: most gaps */
--space-6: 24px; /* Spacious: card padding */
--space-8: 32px; /* Section gaps */
--space-12: 48px; /* Major sections */
--space-16: 64px; /* Page sections */
--space-20: 80px; /* Hero spacing */
}
내부 간격 vs 외부 간격
내부 간격 (padding): 요소 내부의 공간 외부 간격 (margin): 요소 사이의 공간
원칙: 관련된 그룹 내에서 내부 간격은 일반적으로 외부 간격보다 커야 합니다.
.card {
padding: 24px; /* Internal: spacious */
margin-bottom: 16px; /* External: less than padding */
}
컴포넌트 간격 패턴
카드:
.card { padding: 24px; border-radius: 12px; }
.card-header { margin-bottom: 16px; }
.card-title { margin-bottom: 4px; } /* Tight to subtitle */
버튼:
.btn { padding: 12px 24px; border-radius: 8px; }
.btn--sm { padding: 8px 16px; }
.btn--lg { padding: 16px 32px; }
.btn-group { display: flex; gap: 12px; }
폼:
.form-row { margin-bottom: 24px; }
.form-label { margin-bottom: 4px; }
.form-help { margin-top: 4px; }
.form-actions { margin-top: 32px; display: flex; gap: 12px; }
간격 빠른 참조
| 맥락 | 권장 간격 |
|---|---|
| 아이콘과 텍스트 사이 | 4-8px |
| 라벨과 입력 필드 사이 | 4px |
| 폼 그룹 사이 | 24px |
| 카드 패딩 | 20-24px |
| 카드 간격 | 16-24px |
| 섹션 패딩 (모바일) | 48-64px |
| 섹션 패딩 (데스크톱) | 80-96px |
| 버튼 패딩 (가로/세로) | 24px / 12px |
애니메이션 원칙
“애니메이션은 움직이는 그림의 예술이 아니라, 그려지는 움직임의 예술입니다.” — Norman McLaren
애니메이션은 인터페이스에 생명을 불어넣습니다. 잘 만들면 시선을 유도하고, 상태를 전달하며, 감정적 연결을 만들어냅니다. 잘못 만들면 사용자를 좌절시키고 산만하게 합니다.
핵심 원칙
애니메이션은 장식이 아니라 필연적으로 느껴져야 합니다.
좋은 애니메이션: 1. 정적인 디자인으로는 전달할 수 없는 것을 전달합니다 2. 관계를 보여줌으로써 인지 부하를 줄입니다 3. 자연스럽고 예상 가능하게 느껴집니다 4. 의식적 인식에서 사라집니다
나쁜 애니메이션: 1. “멋져 보여서”라는 이유만으로 존재합니다 2. 사용자의 속도를 늦춥니다 3. 스스로에게 시선을 끕니다 4. 불안이나 조급함을 만들어냅니다
UI를 위한 핵심 원칙
1. 예비 동작 — 다음에 일어날 일을 사용자에게 준비시킵니다.
.button {
transition: transform 0.1s ease-out;
}
.button:active {
transform: scale(0.97); /* Slight press before action */
}
2. 후속 동작 — 스프링처럼 자연스럽게 안착하며 동작을 완료합니다.
.panel {
transition: transform 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
}
withAnimation(.spring(response: 0.4, dampingFraction: 0.7)) {
isOpen = true
}
3. Ease-In, Ease-Out — 자연에서 일정한 속도로 움직이는 것은 없습니다.
| 커브 | 사용 시점 | 특성 |
|---|---|---|
ease-out |
요소 진입 시 | 빠르게 시작, 부드럽게 정지 |
ease-in |
요소 퇴장 시 | 부드럽게 시작, 빠르게 퇴장 |
ease-in-out |
상태 변경 시 | 전체적으로 부드러움 |
linear |
로딩 표시기 | 연속적, 기계적 |
4. 스테이징 — 중요한 것에 시선을 유도합니다. 그룹으로 안무된 경우가 아니라면 한 번에 하나의 요소만 움직여야 합니다.
5. 시차 등장 — 요소는 한꺼번에가 아니라 순차적으로 나타나야 합니다.
.list-item {
animation: fadeSlideIn 0.3s ease-out both;
}
.list-item:nth-child(1) { animation-delay: 0ms; }
.list-item:nth-child(2) { animation-delay: 50ms; }
.list-item:nth-child(3) { animation-delay: 100ms; }
.list-item:nth-child(4) { animation-delay: 150ms; }
@keyframes fadeSlideIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
타이밍 가이드라인
| 지속 시간 | 사용 사례 | 느낌 |
|---|---|---|
| 50-100ms | 마이크로 인터랙션 (호버, 누르기) | 즉각적 피드백 |
| 150-200ms | 단순 상태 변경 (토글, 선택) | 경쾌함 |
| 250-350ms | 중간 전환 (패널 슬라이드, 카드 뒤집기) | 부드러움 |
| 400-500ms | 큰 움직임 (페이지 전환, 모달) | 의도적 |
성능: 황금 규칙
transform과 opacity만 애니메이션하세요 — 이 속성들은 GPU 가속이 적용되며 레이아웃 재계산을 유발하지 않습니다.
/* BAD: Animating layout */
.panel { transition: left 0.3s, width 0.3s; }
/* GOOD: Using transform */
.panel { transition: transform 0.3s; }
애니메이션을 사용하지 말아야 할 때
-
사용자가
prefers-reduced-motion을 활성화한 경우css @media (prefers-reduced-motion: reduce) { *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; } } -
애니메이션이 아무런 정보도 전달하지 않는 경우 — 불필요한 스피너, 튀어오르는 요소
- 사용자가 서두르는 경우 — 오류 상태, 폼 검증, 검색 결과
- 애니메이션이 반복 작업을 느리게 만드는 경우 — 키보드 단축키는 애니메이션을 건너뛰어야 합니다
- 데이터가 이미 로드된 경우 — Bear는 콘텐츠를 미리 로드하여 로딩 상태를 제로로 만들어 앱이 즉각적으로 느껴지게 합니다. 미리 로드할 수 있다면 스켈레톤이나 스피너를 완전히 건너뛰세요.
// Bear's approach: preload so no loading state is needed
struct NoteListView: View {
@Query var notes: [Note] // SwiftData loads from disk instantly
// No loading state, no skeleton, no spinner — data is always there
var body: some View {
List(notes) { note in
NoteRow(note: note)
}
}
}
애니메이션 빠른 참조
:root {
/* Durations */
--duration-instant: 0.1s;
--duration-fast: 0.15s;
--duration-normal: 0.25s;
--duration-slow: 0.4s;
/* Easings */
--ease-out: cubic-bezier(0.0, 0.0, 0.58, 1.0);
--ease-in: cubic-bezier(0.42, 0.0, 1.0, 1.0);
--ease-in-out: cubic-bezier(0.42, 0.0, 0.58, 1.0);
--ease-out-back: cubic-bezier(0.34, 1.56, 0.64, 1);
}
인터랙션 패턴
“최고의 인터페이스는 인터페이스가 없는 것이다.” — Golden Krishna
인터랙션 패턴은 사용자가 제품을 조작하고, 탐색하고, 이해하는 방식을 정의합니다. 이 패턴들은 뛰어난 인터랙션을 제공하는 제품들을 연구하여 추출한 것입니다.
직접 조작 (Framer)
추상적인 개념을 직관적으로 만드세요. Framer는 추상적인 숫자에 불과한 CSS 브레이크포인트를 드래그 가능한 핸들로 변환합니다. 사용자는 레이아웃이 실시간으로 적응하는 것을 볼 수 있습니다.
/* Breakpoint handle styling */
.breakpoint-handle {
position: absolute;
top: 0;
bottom: 0;
width: 4px;
background: var(--accent);
cursor: col-resize;
opacity: 0.6;
transition: opacity 0.15s ease;
}
.breakpoint-handle:hover,
.breakpoint-handle:active {
opacity: 1;
width: 6px;
}
사용 시점: 결과가 시각적인 모든 설정—드래그로 크기 조절, 색상 선택기, 타임라인 스크러빙 등.
맥락 인식 인터페이스 (Flighty, Figma)
현재 순간에 관련된 것만 보여주세요. Flighty는 항공편 추적을 위해 15개의 고유한 상태를 사용합니다. Figma의 속성 패널은 선택한 항목에 따라 완전히 달라집니다.
| 단계 (Flighty) | 사용자에게 표시되는 내용 |
|---|---|
| 출발 24시간 전 | 확인 코드, 터미널 정보 |
| 공항 도착 시 | 게이트 번호, 탑승 시간 |
| 비행 중 | 남은 시간, 진행 상황, 도착 예정 시간 |
| 착륙 시 | 환승 게이트, 이동 경로 |
enum ContextState: CaseIterable {
case farOut, dayBefore, headToAirport, atAirport
case atGate, boarding, inFlight, landed, connection
static func current(for flight: Flight, context: UserContext) -> ContextState {
// Factor in: time, location, flight status
// Return the single most relevant state
}
}
안티패턴: 모든 컨트롤을 표시하고 관련 없는 것들을 회색으로 비활성화하는 것. 이는 시각적 노이즈를 만듭니다.
지능형 활성화 (Halide)
도구는 맥락을 감지하고 스스로 활성화되어야 합니다. Halide의 포커스 루페는 포커스 드래그 시 나타나고 손을 떼면 사라집니다. 토글 버튼이 필요 없습니다.
struct IntelligentlyActivated<Content: View>: ViewModifier {
let isInteracting: Bool
@State private var isVisible = false
func body(content: Content) -> some View {
content
.opacity(isVisible ? 1 : 0)
.scaleEffect(isVisible ? 1 : 0.95)
.animation(.easeInOut(duration: 0.2), value: isVisible)
.onChange(of: isInteracting) { _, newValue in
if newValue {
withAnimation { isVisible = true }
} else {
DispatchQueue.main.asyncAfter(deadline: .now() + 0.3) {
if !isInteracting { isVisible = false }
}
}
}
}
}
듀얼 모드 디자인 (Halide, Warp)
모드 전환은 단순히 요소를 토글하는 것이 아니라 UI를 완전히 변환해야 합니다. Halide의 Auto 모드와 Manual 모드는 완전히 다른 인터페이스입니다. Warp는 네 가지 입력 방식(타이핑, 팔레트, AI, 마우스)을 통해 CLI와 GUI를 연결하면서도 사용자를 하나의 패러다임에 가두지 않습니다.
구조화된 콘텐츠 (Warp, Bear, Craft)
전통적으로 비구조화된 콘텐츠에 구조를 부여하세요. Warp는 터미널 출력을 복사, 공유, 재실행이 가능한 개별 블록으로 만듭니다. Bear는 글을 쓰면서 인라인으로 노트를 정리할 수 있게 해줍니다(#tag/subtag). Craft는 어떤 블록이든 페이지가 될 수 있어—미리 정해진 계층 구조가 아니라 사용에서 구조가 자연스럽게 생겨납니다.
점진적 학습 (Superhuman)
반복 노출을 통해 사용자에게 빠른 방법을 가르치세요. Superhuman의 Cmd+K 팔레트는 결과 옆에 항상 키보드 단축키를 표시합니다. 매번 사용할 때마다 마이크로 레슨이 됩니다.
/* Always show shortcut alongside command name */
.command-result {
display: flex;
justify-content: space-between;
padding: 8px 12px;
}
.command-shortcut {
font-family: var(--font-mono);
font-size: 12px;
color: var(--text-muted);
background: var(--bg-subtle);
padding: 2px 6px;
border-radius: 4px;
}
안티패턴: 기능을 설명하는 튜토리얼 모달. 설명은 잊히지만, 실습은 기억됩니다.
AI 인터페이스 패턴
“최고의 AI 인터페이스는 기계의 프로세스를 가시화하고 기계의 출력을 검증 가능하게 만든다.”
AI 인터페이스는 고유한 과제에 직면합니다: 사용자가 출력을 예측할 수 없고, 검사만으로 정확성을 확인할 수 없으며, 시스템이 제대로 작동하는지 오류가 발생했는지 구분하기 어려운 경우가 많습니다.
핵심 문제
| 전통적인 소프트웨어 | AI 소프트웨어 |
|---|---|
| 출력이 예측 가능함 | 출력이 달라짐 |
| 오류가 명확함 | 오류가 그럴듯해 보임 |
| 사용자가 테스트로 검증 | 사용자가 출처 확인으로 검증 |
| 로딩 = 대기 | 로딩 = 작업 중 (보여주세요) |
| 기본적으로 신뢰 | 신뢰를 획득해야 함 |
출처 우선 디자인 (Perplexity)
모든 사실적 주장은 출처와 연결되어야 합니다. Perplexity는 모든 주장에 인라인 인용 [1]을 삽입하며, 호버 미리보기와 고정된 출처 패널을 제공합니다.
.citation-marker {
position: relative;
color: var(--accent);
cursor: pointer;
font-size: 0.8em;
vertical-align: super;
}
.citation-preview {
position: absolute;
bottom: 100%;
left: 50%;
transform: translateX(-50%);
width: 280px;
padding: 12px;
background: var(--bg-elevated);
border: 1px solid var(--border);
border-radius: 8px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
안티패턴: 추적 가능한 출처 없이 주장을 생성하는 AI 인터페이스. 모델이 인용할 수 없다면, 인터페이스에서 이를 표시해야 합니다.
스트리밍 단계 표시기 (Perplexity)
AI가 단순히 작동 중이라는 것이 아니라, 무엇을 하고 있는지 사용자에게 보여주세요. 일반적인 스피너를 단계 표시기로 대체하세요: “검색 중…” → “4개 출처 읽는 중…” → “답변 작성 중…”
.phase-indicator {
display: inline-flex;
align-items: center;
gap: 8px;
padding: 6px 12px;
background: color-mix(in srgb, var(--phase-color) 10%, transparent);
border-radius: 16px;
font-size: 13px;
color: var(--phase-color);
transition: all 0.3s ease;
}
.loading-dots span {
width: 4px;
height: 4px;
background: currentColor;
border-radius: 50%;
animation: pulse 1.4s ease-in-out infinite;
}
오류 투명성
AI가 실패하거나 불확실할 때, 자신감 있는 텍스트 뒤에 숨기지 말고 명확하게 보여주세요.
| 상황 | 나쁜 패턴 | 좋은 패턴 |
|---|---|---|
| 낮은 신뢰도 | 자신 있게 진술 | “확실하지 않지만…” 과 함께 낮은 스타일링 적용 |
| 출처 없음 | 텍스트를 조작 | “이 주장에 대한 출처를 찾을 수 없습니다” |
| 상충하는 출처 | 하나를 조용히 선택 | 충돌을 강조하여 양쪽 모두 표시 |
| 오래된 정보 | 최신인 것처럼 제시 | “[날짜] 기준…” 과 함께 최신성 표시기 적용 |
핵심 통찰: 사용자는 불확실성에 대해 솔직한 AI를 용서합니다. 자신 있게 틀리는 AI는 용서하지 않습니다.
Dieter Rams: 좋은 디자인의 10가지 원칙
“더 적게, 그러나 더 좋게.” — Dieter Rams
Dieter Rams는 20세기 가장 영향력 있는 산업 디자이너입니다. 1961년부터 1995년까지 Braun의 디자인 책임자로서, 수십 년이 지나도 변치 않는 시대를 초월한 제품들을 만들었습니다. 그의 작업은 Apple의 디자인 언어에 직접적인 영감을 주었습니다.
좋은 디자인의 10가지 원칙
1. 좋은 디자인은 혁신적이다 복사하지 마세요. 발전하는 기술과 혁신적인 디자인을 결합하세요.
2. 좋은 디자인은 제품을 유용하게 만든다 모든 요소는 목적을 가져야 합니다. 형태는 기능을 따릅니다.
3. 좋은 디자인은 아름답다 아름다움은 피상적인 것이 아니라 본질적입니다. 매일 사용하는 제품은 우리의 안녕에 영향을 미칩니다.
4. 좋은 디자인은 제품을 이해하기 쉽게 만든다 사용자에게 설명서가 필요하지 않아야 합니다. 인터페이스가 스스로를 가르칩니다.
5. 좋은 디자인은 눈에 띄지 않는다 디자인은 압도하는 것이 아니라 지원해야 합니다. 주인공은 UI가 아니라 사용자의 콘텐츠입니다.
/* Obtrusive: UI competes with content */
.editor {
background: linear-gradient(135deg, purple, blue);
border: 3px dashed gold;
}
/* Unobtrusive: UI recedes, content shines */
.editor {
background: var(--color-background);
border: 1px solid var(--color-border);
}
6. 좋은 디자인은 정직하다 다크 패턴을 사용하지 마세요. 과대 약속하지 마세요. 한계에 대해 투명하게 하세요.
7. 좋은 디자인은 오래간다 빠르게 구식이 될 트렌드를 피하세요. 유행보다 클래식을 선택하세요.
TRENDY (will date): TIMELESS:
- Extreme glassmorphism - Clean typography
- Neon colors, glitch effects - Subtle elevation
- Aggressive gradients - Neutral palette with considered accent
8. 좋은 디자인은 마지막 디테일까지 철저하다 아무것도 임의적이어서는 안 됩니다. 로딩 상태, 빈 상태, 오류 상태—모두 디자인되어야 합니다.
9. 좋은 디자인은 환경 친화적이다 성능은 환경적입니다. 사용자의 주의력을 존중하세요. 효율적인 코드를 작성하세요.
10. 좋은 디자인은 가능한 한 최소한의 디자인이다 필요하지 않은 모든 것을 제거하세요. 최고의 디자인은 보이지 않습니다.
2026년 웹 패턴
현대 웹 디자인은 네이티브 CSS 기능을 활용하여 많은 경우 JavaScript 없이도 구현이 가능합니다. 2025-2026년에는 앵커 포지셔닝, 스크롤 기반 애니메이션, @starting-style이 프로덕션 브라우저에 도입되었습니다.
Container Queries
뷰포트가 아닌 컨테이너를 기준으로 컴포넌트 크기를 조정합니다.
.card-grid {
container-type: inline-size;
container-name: card-grid;
}
.card {
display: grid;
gap: 16px;
padding: 20px;
}
@container card-grid (min-width: 400px) {
.card {
grid-template-columns: auto 1fr;
}
}
@container card-grid (min-width: 600px) {
.card {
padding: 32px;
gap: 24px;
}
}
:has() 선택자
자식 요소를 기반으로 부모를 선택할 수 있습니다—이전에는 JavaScript 없이 불가능했던 기능입니다.
/* Card with image gets different padding */
.card:has(img) {
padding: 0;
}
.card:has(img) .card-content {
padding: 20px;
}
/* Form group with error */
.form-group:has(.input:invalid) .form-label {
color: var(--color-error);
}
/* Highlight navigation when on that page */
.nav-item:has(a[aria-current="page"]) {
background: var(--color-surface);
}
CSS 네이팅
전처리기 없이 네이티브로 중첩 작성이 가능합니다.
.card {
background: var(--color-surface);
border-radius: 12px;
padding: 24px;
& .card-title {
font-size: 1.25rem;
font-weight: 600;
margin-bottom: 8px;
}
& .card-body {
color: var(--color-text-secondary);
line-height: 1.6;
}
&:hover {
box-shadow: var(--shadow-md);
}
@media (min-width: 768px) {
padding: 32px;
}
}
HTMX 통합
무거운 JavaScript 프레임워크 없이 서버 기반 인터랙티비티를 구현합니다.
<!-- Load content on click -->
<button hx-get="/api/more-items"
hx-target="#item-list"
hx-swap="beforeend"
hx-indicator="#loading">
Load More
</button>
<!-- Form with inline validation -->
<form hx-post="/api/contact"
hx-target="#form-response"
hx-swap="outerHTML">
<input type="email" name="email"
hx-post="/api/validate-email"
hx-trigger="blur"
hx-target="next .error" />
<span class="error"></span>
</form>
앵커 포지셔닝
한 요소를 다른 요소에 상대적으로 배치하는 네이티브 CSS 기능으로, JavaScript가 필요 없습니다. 툴팁, 팝오버, 드롭다운 메뉴가 트리거 요소를 자동으로 따라갑니다.
/* Anchor an element to another */
.trigger {
anchor-name: --my-trigger;
}
.tooltip {
position: fixed;
position-anchor: --my-trigger;
top: anchor(bottom);
left: anchor(center);
translate: -50% 8px;
}
스크롤 기반 애니메이션
애니메이션 진행 상태를 스크롤 위치에 연동합니다. 읽기 진행률 표시, 패럴랙스 효과, 순차 공개 시퀀스를 JavaScript 없이 구현할 수 있습니다.
/* Reading progress bar */
.progress-bar {
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 3px;
background: var(--color-primary);
transform-origin: left;
animation: progress linear;
animation-timeline: scroll();
}
@keyframes progress {
from { transform: scaleX(0); }
to { transform: scaleX(1); }
}
@starting-style
DOM에 진입하는 요소의 초기 스타일을 정의하여, JavaScript 없이 CSS만으로 진입 애니메이션을 구현할 수 있습니다.
.card {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s ease, transform 0.3s ease;
@starting-style {
opacity: 0;
transform: translateY(10px);
}
}
디자인 토큰 시스템
애플리케이션 전반의 일관성을 위한 완전한 토큰 시스템입니다.
:root {
/* Colors */
--color-text: #1a1a1a;
--color-text-secondary: #666666;
--color-text-muted: #999999;
--color-background: #ffffff;
--color-surface: #f8f9fa;
--color-surface-elevated: #ffffff;
--color-border: #e5e7eb;
--color-primary: #3b82f6;
--color-primary-hover: #2563eb;
--color-success: #10b981;
--color-warning: #f59e0b;
--color-error: #ef4444;
/* Typography */
--font-sans: system-ui, -apple-system, sans-serif;
--font-mono: "SF Mono", Consolas, monospace;
--text-xs: 0.75rem;
--text-sm: 0.875rem;
--text-base: 1rem;
--text-lg: 1.125rem;
--text-xl: 1.25rem;
--text-2xl: 1.5rem;
--text-3xl: 2rem;
--leading-tight: 1.25;
--leading-normal: 1.5;
--leading-relaxed: 1.75;
/* Spacing (8px base) */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-3: 0.75rem; /* 12px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
--space-12: 3rem; /* 48px */
--space-16: 4rem; /* 64px */
/* Borders */
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
--radius-full: 9999px;
/* Shadows */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
--shadow-lg: 0 10px 15px rgba(0, 0, 0, 0.1);
/* Transitions */
--ease-out: cubic-bezier(0.16, 1, 0.3, 1);
--duration-fast: 100ms;
--duration-normal: 200ms;
}
올바른 다크 모드 구현
단순히 색상을 반전시키지 말고, 어두운 환경에 맞게 재설계해야 합니다.
@media (prefers-color-scheme: dark) {
:root {
/* Neutrals */
--color-background: hsl(220, 13%, 10%);
--color-surface: hsl(220, 13%, 15%);
--color-surface-elevated: hsl(220, 13%, 18%);
--color-border: hsl(220, 13%, 23%);
/* Text (inverted) */
--color-text: hsl(220, 9%, 93%);
--color-text-secondary: hsl(220, 9%, 70%);
--color-text-muted: hsl(220, 9%, 55%);
/* Adjust saturation for dark mode */
--color-primary: hsl(220, 80%, 60%);
--color-success: hsl(142, 70%, 45%);
--color-error: hsl(0, 80%, 65%);
/* Shadows in dark mode need adjustment */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
}
}
다크 모드 원칙: - 넓은 면적에는 채도를 낮추세요 - 강조 색상의 명도를 높이세요 - 그림자를 강화하세요 (더 높은 대비가 필요합니다) - 다크 모드는 부수적인 요소가 아닌, 의도적으로 설계해야 합니다
Figma 추출 워크플로우
디자인 파일을 프로덕션 코드로 변환하려면 디자인 언어를 정의하는 디자인 토큰—색상, 타이포그래피, 간격, 효과—을 체계적으로 추출해야 합니다.
Figma Variables 내보내기
Figma의 네이티브 Variables 기능은 가장 깔끔한 추출 경로를 제공합니다.
내보내기 단계:
1. Figma 파일 열기 → Local Variables 패널
2. 컬렉션 메뉴 클릭 → “Export to JSON”
3. figma-variables.json으로 저장
JSON 토큰 구조:
{
"colors": {
"primitive": {
"blue-500": { "value": "#3b82f6", "type": "color" },
"blue-600": { "value": "#2563eb", "type": "color" }
},
"semantic": {
"primary": { "value": "{colors.primitive.blue-500}", "type": "color" },
"primary-hover": { "value": "{colors.primitive.blue-600}", "type": "color" }
}
},
"spacing": {
"1": { "value": "4px", "type": "spacing" },
"2": { "value": "8px", "type": "spacing" },
"4": { "value": "16px", "type": "spacing" }
}
}
토큰에서 CSS로의 변환
CSS 사용자 정의 속성:
:root {
/* Primitive colors (direct values) */
--color-blue-50: #eff6ff;
--color-blue-100: #dbeafe;
--color-blue-500: #3b82f6;
--color-blue-600: #2563eb;
--color-blue-900: #1e3a8a;
/* Semantic colors (reference primitives) */
--color-primary: var(--color-blue-500);
--color-primary-hover: var(--color-blue-600);
--color-background: var(--color-white);
--color-surface: var(--color-gray-50);
/* Spacing (8px grid) */
--space-1: 0.25rem; /* 4px */
--space-2: 0.5rem; /* 8px */
--space-4: 1rem; /* 16px */
--space-6: 1.5rem; /* 24px */
--space-8: 2rem; /* 32px */
/* Typography */
--font-size-sm: 0.875rem;
--font-size-base: 1rem;
--font-size-lg: 1.125rem;
--line-height-tight: 1.25;
--line-height-normal: 1.5;
/* Effects */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.05);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.07);
--radius-sm: 4px;
--radius-md: 8px;
--radius-lg: 12px;
}
다크 모드 토큰:
@media (prefers-color-scheme: dark) {
:root {
--color-background: var(--color-gray-900);
--color-surface: var(--color-gray-800);
--color-text: var(--color-gray-100);
--color-text-secondary: var(--color-gray-400);
/* Adjusted shadows for dark mode */
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.3);
--shadow-md: 0 4px 6px rgba(0, 0, 0, 0.4);
}
}
토큰에서 SwiftUI로의 변환
Color 확장:
import SwiftUI
extension Color {
// MARK: - Primitive Colors
static let blue50 = Color(hex: "eff6ff")
static let blue500 = Color(hex: "3b82f6")
static let blue600 = Color(hex: "2563eb")
// MARK: - Semantic Colors
static let brandPrimary = Color.blue500
static let brandPrimaryHover = Color.blue600
// MARK: - Surface Colors
static let surfaceBackground = Color(light: .white, dark: Color(hex: "0f172a"))
static let surfaceElevated = Color(light: Color(hex: "f8fafc"), dark: Color(hex: "1e293b"))
}
extension Color {
init(hex: String) {
// Standard hex parsing implementation
}
init(light: Color, dark: Color) {
self.init(UIColor { traits in
traits.userInterfaceStyle == .dark ? UIColor(dark) : UIColor(light)
})
}
}
간격 상수:
enum Spacing {
static let xs: CGFloat = 4 // --space-1
static let sm: CGFloat = 8 // --space-2
static let md: CGFloat = 16 // --space-4
static let lg: CGFloat = 24 // --space-6
static let xl: CGFloat = 32 // --space-8
}
// Usage
VStack(spacing: Spacing.md) {
// ...
}
.padding(Spacing.lg)
디자이너 핸드오프 체크리스트
디자이너가 내보내야 할 항목:
| 에셋 유형 | 형식 | 참고 사항 |
|---|---|---|
| 색상 | Variables JSON | 라이트 + 다크 모드 포함 |
| 타이포그래피 | 스타일 내보내기 | 폰트, 크기, 굵기, 행간 |
| 간격 | Variables JSON | 기본 단위 문서화 |
| 아이콘 | SVG | 아웃라인, 단일 색상 |
| 이미지 | PNG @2x/@3x 또는 WebP | 압축 적용 |
| 컴포넌트 | Figma 링크 | 구현 시 참조용 |
품질 게이트 기준:
- [ ] 모든 색상이 변수로 정의됨 (하드코딩된 hex 값 없음)
- [ ] 타이포그래피가 정의된 텍스트 스타일을 사용함
- [ ] 간격이 그리드 시스템을 따름 (8px 기준)
- [ ] 다크 모드 변형이 제공됨
- [ ] 인터랙티브 상태가 문서화됨 (hover, active, disabled)
- [ ] 반응형 브레이크포인트가 표시됨
- [ ] 접근성 요구 사항이 명시됨 (대비율)
개발자가 받는 산출물:
- 토큰 파일 (플랫폼에 따라 JSON/CSS/Swift)
- 측정값이 포함된 컴포넌트 사양서
- 필요한 형식의 에셋 내보내기
- 인터랙션 문서 (상태, 애니메이션)
- 접근성 어노테이션
빠른 참조 표
Gestalt 원칙
| 원칙 | 규칙 | 활용 |
|---|---|---|
| 근접성 | 관련 요소 = 가까이 배치 | 폼, 섹션 |
| 유사성 | 같은 외형 = 같은 기능 | 버튼, 카드 |
| 전경-배경 | 명확한 레이어 분리 | 모달, 카드 |
| 연속성 | 시선의 흐름을 따름 | 타임라인, 정렬 |
| 폐합 | 뇌가 형태를 완성함 | 아이콘, 스크롤 힌트 |
타이포그래피
| 요소 | 크기 | 굵기 | 행간 |
|---|---|---|---|
| 본문 | 16px | 400 | 1.5-1.7 |
| 제목 | 24-48px | 600-700 | 1.1-1.2 |
| UI 레이블 | 12-14px | 500 | 1.3-1.4 |
| 캡션 | 12px | 400 | 1.4 |
색상 역할
| 역할 | 라이트 모드 | 다크 모드 |
|---|---|---|
| 배경 | #ffffff | #0f172a |
| 표면 | #f4f5f7 | #1e293b |
| 테두리 | #e4e6ea | #334155 |
| 텍스트 | #1a1a2e | #f1f5f9 |
| 보조 텍스트 | #6b7280 | #94a3b8 |
| Primary | #3b82f6 | #60a5fa |
| Success | #22c55e | #4ade80 |
| Error | #ef4444 | #f87171 |
간격 스케일
| 토큰 | 값 | 활용 |
|---|---|---|
| –space-1 | 4px | 아이콘 간격 |
| –space-2 | 8px | 인라인 요소 |
| –space-4 | 16px | 기본 간격 |
| –space-6 | 24px | 카드 패딩 |
| –space-8 | 32px | 섹션 간격 |
| –space-16 | 64px | 페이지 섹션 |
디자인 체크리스트
인터페이스를 배포하기 전에 다음 항목을 확인하세요:
Gestalt
- [ ] 관련 요소가 관련 없는 요소보다 더 가까이 배치되어 있는가 (근접성)
- [ ] 유사한 기능이 유사한 스타일을 가지고 있는가 (유사성)
- [ ] 전경과 배경이 명확하게 구분되는가 (전경-배경)
- [ ] 시선이 레이아웃을 따라 자연스럽게 흐르는가 (연속성)
타이포그래피
- [ ] 기본 글꼴 크기가 최소 16px 이상인가
- [ ] 본문 텍스트의 행간이 1.5 이상인가
- [ ] 줄 길이가 75자 이하인가
- [ ] 계층 구조가 명확한가 (3단계가 구분 가능)
- [ ] 일관된 스케일이 전체적으로 사용되고 있는가
색상
- [ ] 모든 텍스트가 4.5:1 대비를 충족하는가 (WCAG AA)
- [ ] 색상만이 유일한 정보 전달 수단이 아닌가 (아이콘/레이블도 함께 사용)
- [ ] 다크 모드가 의도적으로 설계되었는가
- [ ] 60-30-10 색상 비율을 따르고 있는가
시각적 계층 구조
- [ ] 가장 중요한 요소를 즉시 식별할 수 있는가
- [ ] 시선이 의도한 순서대로 이동하는가
- [ ] 섹션당 하나의 명확한 CTA가 있는가
- [ ] 타입 스케일이 일관적인가
여백
- [ ] 모든 여백이 정의된 스케일을 사용하는가 (임의의 숫자 사용 금지)
- [ ] 카드/컴포넌트의 패딩이 일관적인가
- [ ] 모바일 여백이 편안한가
- [ ] 그리드 정렬이 일관적인가 (8px 기준)
인터랙션
- [ ] 사용자가 도구에 대해 고민하지 않고 목표를 달성할 수 있는가?
- [ ] UI가 현재 맥락에 맞게 적응하는가?
- [ ] 도구가 필요한 상황에서만 표시되는가?
- [ ] 반복 사용을 통해 더 빠른 방법을 자연스럽게 익힐 수 있는가?
AI 인터페이스
- [ ] 모든 사실 기반 주장에 추적 가능한 출처가 있는가?
- [ ] 스트리밍이 단순한 로딩 표시가 아닌 처리 단계를 보여주는가?
- [ ] 오류 상태가 숨겨지지 않고 투명하게 표시되는가?
- [ ] 신뢰도가 낮은 출력이 시각적으로 구분되는가?
Dieter Rams 체크
- [ ] 제거할 수 있는 것이 있는가?
- [ ] 모든 요소가 기능적 역할을 하는가?
- [ ] 5년 후에도 낡아 보이지 않을 것인가?
- [ ] 모든 상태를 디자인했는가?
참고 자료
도서: - As Little Design as Possible — Sophie Lovell 저 (Dieter Rams) - The Elements of Typographic Style — Robert Bringhurst 저
도구: - WebAIM Contrast Checker - Type Scale Generator - Figma Tokens Studio — 디자인 토큰 관리 도구
디자인 시스템: - Apple HIG - Material Design 3 - Radix UI - shadcn/ui
디자인 스터디
16개의 뛰어난 제품을 심층 분석하여, 배울 만한 패턴과 원칙을 정리했습니다.
개발자 도구
| 제품 | 핵심 기여 |
|---|---|
| Figma | 멀티플레이어 프레즌스, 맥락 인식 패널 |
| Warp | 블록 기반 터미널, CLI-GUI 연결 |
| Framer | 비주얼 반응형 디자인, 속성 제어 |
| Vercel | 다크 모드의 우수 사례, 앰비언트 상태 표시 |
| Linear | Optimistic UI, 키보드 중심 워크플로우 |
| Raycast | 확장 시스템, 빠른 실행 |
iOS 네이티브 (Apple Design Award 수상작)
| 제품 | 핵심 기여 |
|---|---|
| Flighty | 15가지 스마트 상태, Live Activities, 데이터 시각화 |
| Halide | 지능형 활성화, 제스처 제어 |
| Bear | 타이포그래피 중심, 인라인 태깅 |
| Craft | 네이티브 우선 크로스 플랫폼, 중첩 페이지 |
| Things | 지연 날짜, 빠른 입력 패턴 |
생산성 및 AI
| 제품 | 핵심 기여 |
|---|---|
| Superhuman | 100ms 규칙, 명령 팔레트 학습, 실습 기반 온보딩 |
| Perplexity | 출처 기반 AI, 스트리밍 단계 |
| Notion | 블록 시스템, 슬래시 명령어 |
| Arc | 스페이스, 분할 뷰, 명령 바 |
| Stripe | 문서화의 우수 사례, API 설계 |
이 가이드는 실천을 통해 성장합니다. 디자인 원칙은 시대를 초월하지만, 그 적용 방식은 기술과 이해의 발전에 따라 진화합니다.