소프트웨어 엔지니어를 위한 디자인 원칙
더 나은 소프트웨어를 만드는 데 필요한 디자인 기본기를 배워보세요. 타이포그래피, 색 이론, 간격, 애니메이션, AI 인터페이스 패턴, Arc부터 Zomato까지 이어지는 48개의 제품 사례 연구까지 함께 살펴보세요.
2026년 5월 18일 업데이트됨
2026년 5월 18일 업데이트: Design Studies 색인을 현재 48개 연구 자료에 맞춰 새로 고치고, AI Interface Patterns에 agentic workflow 근거를 보강했어요. 여기에는 체크포인트, 승인 상태, 출처 신뢰도, 눈에 보이는 검증이 포함돼요.
2026년 5월 업데이트: WCAG 3.0의 2026년 3월 working draft와 WCAG 2.2 및 3.0이 함께 유지된다는 W3C의 입장에 대한 메모를 추가했어요. 현재 접근성 작업에서는 여전히 WCAG 2.2를 기준으로 삼아야 해요.
2026년 2월 업데이트: 새로운 섹션 2개를 추가했어요. Interaction Patterns에는 Framer, Flighty, Halide, Warp, Bear, Craft, Superhuman을 연구해 정리한 8가지 패러다임을 담았고, AI Interface Patterns에는 citation-forward design, 스트리밍 단계, Perplexity에서 확인한 오류 투명성을 담았어요. Web Patterns는 anchor positioning, scroll-driven animations, @starting-style을 반영해 2026년 기준으로 업데이트했어요. 접근성 내용도 WCAG 2.2의 ISO 표준화를 반영하도록 업데이트했어요. 뛰어난 제품 48개의 심층 분석은 Design Studies를 참고하세요.
저는 소프트웨어를 만들며 수년 동안 디자인을 공부해 왔고, Dieter Rams 같은 전설적인 인물의 원칙을 익히고 Linear, Stripe, Raycast 같은 제품의 인터페이스를 분석해 왔어요. 이 가이드는 제가 소프트웨어의 모습과 느낌에 관심을 갖기 시작했을 때 있었으면 했던 종합 참고 자료로, 그동안의 이해를 정리한 것입니다.
디자인은 장식이 아니에요. 커뮤니케이션이에요. 모든 픽셀은 기능, 위계, 의미를 전달해요. 아마추어처럼 느껴지는 소프트웨어와 전문적으로 느껴지는 소프트웨어의 차이는 이런 원칙을 이해하고 일관되게 적용하는 데서 나옵니다.
이 가이드는 이미 코드를 작성할 수 있다고 가정해요. 그리고 보는 법을 알려줍니다. 왜 어떤 인터페이스는 자연스럽고 어떤 인터페이스는 혼란스럽게 느껴지는지, 더 중요하게는 그런 자연스러운 인터페이스를 어떻게 만들 수 있는지를 이해하도록 돕습니다.
목차
Part 1: 기초
Part 2: 인터랙션 & AI
Part 3: 디자인 철학
Part 4: 구현
Part 5: 참고자료
Gestalt 심리학
“전체는 부분의 합과는 다른 무언가이다.” — Kurt Koffka
Gestalt 심리학은 1920년대 독일에서 발전한 이론으로, 인간이 시각 정보를 어떻게 인식하는지 설명해요. 뇌는 개별 픽셀을 보지 않고, 요소들을 의미 있는 패턴으로 조직해요. 이 원칙들을 익히면 사용자가 인터페이스를 어떻게 인식할지 제어할 수 있어요.
근접성
가까이 있는 요소들은 하나의 그룹으로 인식돼요.
UI 디자인에서 가장 강력한 Gestalt 원칙이에요. 여백은 다른 어떤 시각적 속성보다 관계를 더 효과적으로 전달해요.
잘못된 예 (동일한 간격 = 그룹핑 없음):
┌─────────────────┐
│ Label │
│ │
│ Input Field │
│ │
│ Label │
│ │
│ Input Field │
└─────────────────┘
올바른 예 (불균등한 간격 = 명확한 그룹):
┌─────────────────┐
│ Label │
│ Input Field │ ← 좁은 간격(4px) - 관련 요소
│ │
│ │ ← 넓은 간격(24px) - 그룹 분리
│ Label │
│ Input Field │ ← 좁은 간격(4px) - 관련 요소
└─────────────────┘
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
유사성
시각적 특성을 공유하는 요소들은 관련된 것으로 보여요.
요소가 같은 모습이면 사용자는 같은 기능을 한다고 추정해요. 디자인 시스템에서 일관된 버튼 스타일, 카드 처리, 타이포그래피를 사용하는 이유가 바로 이것이에요.
네비게이션 예시:
┌───────────────────────────────────┐
│ [Dashboard] [Projects] [Settings] │ ← 같은 스타일 = 같은 기능
│ │
│ ┌─────┐ ┌─────┐ ┌─────┐ │
│ │Card │ │Card │ │Card │ │ ← 같은 스타일 = 같은 콘텐츠 유형
│ └─────┘ └─────┘ └─────┘ │
│ │
│ [+ New Project] │ ← 다른 스타일 = 다른 기능
└───────────────────────────────────┘
전경-배경 관계
콘텐츠는 배경과 명확히 구분되어야 해요.
뇌는 “전경”(집중할 대상)과 “배경”(뒷면)을 구별해야 해요. 전경-배경 관계가 불분명하면 시각적 혼란이 생겨요.
기법: - 대비 (밝은 전경 위에 어두운 배경, 또는 그 반대) - 그림자 (전경을 배경 위로 들어올림) - 테두리 (전경의 경계를 구분) - 블러 (배경을 흐리게, 전경을 선명하게)
/* 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 */
}
공통 영역
경계 안에 있는 요소들은 그룹으로 인식돼요.
시각적 컨테이너(카드, 박스, 테두리가 있는 영역) 안에 요소를 넣으면 서로 관련이 있다는 신호를 줘요.
연속성
눈은 경로, 선, 곡선을 따라가요.
정렬과 시각적 흐름을 활용하면 인터페이스 전체에 걸쳐 시선을 유도할 수 있어요.
정렬의 연속성:
┌────────────────────────────────┐
│ Logo [Nav] [Nav] [Nav] │ ← 수평축으로 정렬
├────────────────────────────────┤
│ │
│ Headline │
│ ───────────────────────────── │ ← 시선이 왼쪽 끝을 따라감
│ Paragraph text continues │
│ along the same left edge │
│ │
│ [Primary Action] │ ← 여전히 왼쪽 끝에 위치
└────────────────────────────────┘
폐합
뇌는 불완전한 형태를 완성해요.
사용자는 모든 픽셀이 그려져 있을 필요가 없어요—익숙한 형태는 머릿속에서 자동으로 완성해요. 이를 활용하면 더 미니멀하고 우아한 디자인이 가능해요.
/* 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 */
}
행간 (Leading)
행간은 가독성에 큰 영향을 미쳐요. 콘텐츠 유형에 따라 다른 행간이 필요해요.
| 콘텐츠 유형 | 행간 | 이유 |
|---|---|---|
| 헤드라인 | 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 | N/A | N/A |
| 정렬 | 왼쪽 | 가운데 가능 | 왼쪽 |
색채 이론
“색상은 영혼에 직접 영향을 미치는 힘입니다.” — 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 | N/A |
| WCAG 2.2는 2025년 10월에 ISO 표준(ISO/IEC 40500:2025)이 되었고, focus visibility, redundant entry, accessible authentication 기준이 추가되었어요. 주요 추가 사항은 다음과 같아요. focus indicators는 다른 콘텐츠에 완전히 가려지면 안 되고(2.4.11), 인증은 cognitive function tests에만 의존하면 안 돼요(3.3.8). W3C는 2024년 12월에 업데이트된 WCAG 2.2를 공개했으며, 이는 2026년 말까지 ISO/IEC 40500:2026으로 배포될 것으로 예상돼요. |
WCAG 3.0은 아직 작업 초안이에요. 최신 개정판은 2026년 3월에 배포되었고, WCAG 2.2를 대체하지 않아요. W3C는 두 표준이 함께 유지될 것이라고 밝혔어요. Accessibility Guidelines Working Group은 2026년에 예상 WCAG 3 일정을 발표할 계획이지만, 현재 작업에서 목표로 삼아야 할 표준은 여전히 WCAG 2.2예요.
도구: WebAIM Contrast Checker, Chrome DevTools color picker
시각적 위계
“디자인은 브랜드의 조용한 대사입니다.” — Paul Rand
시각적 위계는 사용자가 무엇을 첫 번째, 두 번째, 세 번째로 보게 되는지를 제어해요. 명확한 위계가 없으면 사용자는 정보를 찾기 위해 애써야 해요. 위계가 있으면 인터페이스는 자연스럽고 쉽게 느껴져요.
위계의 6가지 도구
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 (content pages): Z-PATTERN (landing pages):
████████████████████████ 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): 이름 라벨이 붙은 여러 사용자 커서, 선택 강조 표시, component 윤곽선이 살아 있는 문서를 만들어 줍니다. 각 협업자의 색은 서로 구분되지만 비중은 동일해서, 어떤 커서도 다른 커서보다 더 두드러지지 않습니다.
은은한 상태 표시기(Vercel): 배포 상태는 방해가 되는 알림 대신 미묘하고 지속적인 표시기를 사용합니다. 상단의 얇은 색상 막대가 workflow를 끊지 않으면서 상태(빌드 중, 배포됨, 실패)를 전달합니다.
현실 세계 디자인 비유(Flighty): 비행 진행 시각화는 물리적인 항공 계기판을 반영합니다. 고도 곡선, 속도 표시기, 게이트 지도는 추상적인 진행 막대 대신 익숙한 시각적 은유를 사용합니다.
눈을 가늘게 뜨고 보는 테스트
디자인을 눈을 가늘게 뜨고 보세요. 그래도 hierarchy가 보이나요? 그렇다면 강한 디자인입니다.
간격과 리듬
“여백은 공기와 같습니다. 디자인이 숨 쉬려면 꼭 필요합니다.” — Wojciech Zieliński
간격은 디자인의 보이지 않는 구조입니다. 일관된 간격은 시각적 리듬을 만들어 요소들이 하나의 coherent system 안에 함께 속해 있다는 느낌을 줍니다.
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 */
}
내부 간격과 외부 간격
내부 (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 |
| 카드 padding | 20-24px |
| 카드 gap | 16-24px |
| 섹션 padding (모바일) | 48-64px |
| 섹션 padding (데스크톱) | 80-96px |
| 버튼 padding (가로/세로) | 24px / 12px |
| — |
애니메이션 원칙
“애니메이션은 움직이는 그림의 예술이 아니라, 그려진 움직임의 예술이다.” — Norman McLaren
애니메이션은 인터페이스에 생명을 불어넣어요. 잘 활용하면 시선을 유도하고, 상태를 전달하며, 감정적 연결을 만들어 줘요. 잘못 사용하면 사용자를 짜증나게 하고 주의를 분산시켜요.
핵심 원칙
애니메이션은 장식이 아니라 필연적으로 느껴져야 해요.
좋은 애니메이션: 1. 정적인 디자인으로는 전달할 수 없는 정보를 전달해요 2. 관계를 시각적으로 보여줌으로써 인지 부하를 줄여요 3. 자연스럽고 예상 가능하게 느껴져요 4. 의식하지 못할 정도로 자연스럽게 녹아들어요
나쁜 애니메이션: 1. 단지 “멋져 보이기 때문에” 존재해요 2. 사용자를 느리게 만들어요 3. 그 자체로 시선을 끌어요 4. 불안감이나 초조함을 유발해요
UI를 위한 핵심 원칙
1. 예비 동작(Anticipation) — 사용자에게 다음에 일어날 일을 준비시켜요.
.button {
transition: transform 0.1s ease-out;
}
.button:active {
transform: scale(0.97); /* Slight press before action */
}
2. 관성(Follow-Through) — 모션이 스프링처럼 자연스럽게 안착하도록 해요.
.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. 연출(Staging) — 중요한 것에 시선을 유도하세요. 그룹으로 안무된 경우가 아니라면 한 번에 하나만 움직여야 해요.
5. 시차 배치(Staggering) — 요소들은 한꺼번에가 아니라 순차적으로 등장해야 해요.
.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의 자동 모드와 수동 모드는 완전히 다른 인터페이스예요. 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]을 넣고, hover 미리보기와 계속 표시되는 출처 패널을 제공합니다. |
.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는 용납하지 않습니다. |
에이전트형 워크플로 근거
AI 에이전트에는 노력을 설명하는 데 그치지 않고 작업을 증명하는 인터페이스 상태가 필요해요. 좋은 에이전트형 워크플로는 계획 승인, 소스 수집, 편집 적용, 테스트 실행, 검토 대기, 릴리스 차단 또는 준비 완료 같은 체크포인트를 통해 진행 상황을 읽기 쉽게 보여줘요.
| 에이전트 상태 | 인터페이스 패턴 | 중요한 이유 |
|---|---|---|
| 계획 | 명확한 목표와 수락 기준 | 에이전트가 결과가 아니라 활동 자체에 최적화하지 않도록 막아줘요 |
| 탐색 | 신뢰도와 최신성이 포함된 소스 목록 | 사용자가 검증된 사실과 작업 가정을 구분할 수 있게 해줘요 |
| 실행 | 체크포인트와 연결된 변경 파일 요약 | 전체 transcript를 읽지 않아도 편집 내용을 검토할 수 있게 해줘요 |
| 검증 | 테스트, 경로, screenshot, 또는 runtime 증거 | 증명과 그럴듯한 자신감을 구분해줘요 |
| 릴리스 | 승인, 배포, 또는 native review 상태 | 초록색 local check 뒤에 미완성 작업이 숨어 공개되지 않게 해줘요 |
| 안티패턴: 여러 단계로 이루어진 에이전트 작업에 단일 “완료” 상태만 사용하는 것. 복잡한 작업에는 무엇이 바뀌었는지, 무엇이 그것을 증명했는지, 무엇을 거부했는지, 그리고 아직 사람의 판단이 필요한 부분이 무엇인지 보여주는 가시적인 관리 흐름이 필요해요. |
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
현대 웹 디자인은 많은 경우 JavaScript의 필요성을 없애는 기본 CSS 기능을 활용합니다. 2025-2026년에는 anchor positioning, scroll-driven animations, @starting-style가 프로덕션 브라우저에 도입되었습니다.
Container Queries
컴포넌트 크기는 viewport가 아니라 해당 컴포넌트를 담는 container를 기준으로 정하세요.
.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>
Anchor Positioning
JavaScript 없이 한 요소를 다른 요소에 상대적으로 배치하는 네이티브 CSS 포지셔닝입니다. 트리거를 따라 움직이는 툴팁, 팝오버, 드롭다운 메뉴를 만들 수 있어요.
/* 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 없이 읽기 진행 표시기, parallax 효과, reveal 시퀀스를 구현할 수 있습니다.
/* 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만으로 enter 애니메이션을 구현할 수 있습니다.
.card {
opacity: 1;
transform: translateY(0);
transition: opacity 0.3s ease, transform 0.3s ease;
@starting-style {
opacity: 0;
transform: translateY(10px);
}
}
Design tokens 시스템
애플리케이션 전반에서 일관성을 유지하기 위한 완전한 token 시스템입니다.
: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 추출 워크플로
디자인 파일을 프로덕션 코드로 변환하려면 색상, 타이포그래피, 간격, 효과처럼 디자인 언어를 정의하는 디자인 tokens를 체계적으로 추출해야 해요.
Figma Variables 내보내기
Figma의 기본 Variables 기능은 가장 깔끔한 추출 경로를 제공해요:
내보내기 단계:
1. Figma 파일 열기 → Local Variables 패널
2. 컬렉션 메뉴 클릭 → “JSON로 내보내기”
3. figma-variables.json으로 저장
JSON Token 구조:
{
"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" }
}
}
Token에서 CSS로 변환
CSS Custom Properties:
: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;
}
다크 모드 tokens:
@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);
}
}
Token-to-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 | 라이트 모드 + 다크 모드 포함 |
| 타이포그래피 | 스타일 내보내기 | 폰트, 크기, 굵기, line-height |
| 간격 | Variables JSON | 기준 단위 문서화 |
| 아이콘 | SVG | 외곽선형, 단색 |
| 이미지 | PNG @2x/@3x 또는 WebP | 압축 적용 |
| 컴포넌트 | Figma 링크 | 구현 중 참고용 |
| 품질 검증 기준: |
- [ ] 모든 색상이 변수로 정의되어 있습니다(하드코딩된 hex 없음)
- [ ] 타이포그래피가 정의된 텍스트 스타일을 사용합니다
- [ ] 간격이 그리드 시스템을 따릅니다(8px 기준)
- [ ] 다크 모드 variants가 제공됩니다
- [ ] 상호작용 상태가 문서화되어 있습니다(hover, active, disabled)
- [ ] 반응형 breakpoints가 주석으로 표시되어 있습니다
- [ ] 접근성 요구 사항이 기록되어 있습니다(contrast ratios)
개발자가 받는 항목:
- Token 파일(JSON/CSS/Swift, 플랫폼에 따라 다름)
- 측정값이 포함된 컴포넌트 사양
- 필요한 형식으로 내보낸 에셋
- 상호작용 문서(상태, 애니메이션)
- 접근성 주석
빠른 참조 표
Gestalt Principles
| Principle | 규칙 | 사용 |
|---|---|---|
| 근접성 | 관련된 것은 가깝게 | 양식, 섹션 |
| 유사성 | 같은 모습은 같은 기능 | 버튼, 카드 |
| 전경-배경 | 명확한 레이어 구분 | 모달, 카드 |
| 연속성 | 선을 따라 흐름 | 타임라인, 정렬 |
| 폐쇄성 | 뇌가 형태를 완성함 | 아이콘, 스크롤 힌트 |
| ### Typography | ||
| 요소 | 크기 | 굵기 |
| --------- | ------ | -------- |
| 본문 | 16px | 400 |
| 헤드라인 | 24-48px | 600-700 |
| UI 레이블 | 12-14px | 500 |
| 캡션 | 12px | 400 |
| ### 색상 역할 | ||
| 역할 | 라이트 모드 | 다크 모드 |
| ------ | ------------ | ----------- |
| 배경 | #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)
- [ ] 색상만 유일한 표시 수단이 아닙니다 (아이콘/레이블도 사용)
- [ ] dark mode가 의도적으로 설계되어 있습니다
- [ ] 60-30-10 분포를 따릅니다
시각적 위계
- [ ] 가장 중요한 #1 요소를 식별할 수 있습니다
- [ ] 시선이 의도한 순서대로 이동합니다
- [ ] 섹션마다 명확한 CTA가 1개 있습니다
- [ ] 타입 스케일이 일관됩니다
간격
- [ ] 모든 간격이 정의된 스케일을 사용합니다 (magic number 없음)
- [ ] 카드/컴포넌트의 padding이 일관됩니다
- [ ] 모바일 간격이 편안합니다
- [ ] Grid 정렬이 일관됩니다 (8px 기준)
상호작용
- [ ] 사용자가 도구를 의식하지 않고 목표를 달성할 수 있나요?
- [ ] UI가 현재 맥락에 맞게 조정되나요?
- [ ] 도구는 관련이 있을 때만 표시되나요?
- [ ] 반복해서 사용할수록 더 빠른 방법을 익히게 되나요?
AI 인터페이스
- [ ] 모든 사실 기반 주장에는 추적 가능한 출처가 있습니다
- [ ] Streaming은 단순한 spinner가 아니라 처리 단계를 보여 줍니다
- [ ] 오류 상태는 숨겨지지 않고 투명하게 드러납니다
- [ ] 신뢰도가 낮은 출력은 시각적으로 구분됩니다
Dieter Rams 점검
- [ ] 제거할 수 있는 것이 있나요?
- [ ] 모든 요소가 기능을 수행하나요?
- [ ] 5년 뒤에도 구식처럼 느껴지지 않을까요?
- [ ] 모든 상태를 설계했나요?
자료
책: - Sophie Lovell의 As Little Design as Possible (Dieter Rams) - Robert Bringhurst의 The Elements of Typographic Style
도구: - WebAIM Contrast Checker - Type Scale Generator - Figma Tokens Studio — Design token 관리
디자인 시스템: - Apple HIG - Material Design 3 - Radix UI - shadcn/ui
디자인 사례 연구
48개의 뛰어난 제품을 깊이 살펴보며, 참고할 만한 패턴과 원칙을 기록합니다.
개발자 도구 및 AI
| 제품 | 주요 기여 |
|---|---|
| Figma | 멀티플레이어 presence, 맥락 인식 패널 |
| Warp | 블록 기반 터미널, CLI-GUI 브리지 |
| Framer | 시각적 반응형 디자인, 속성 컨트롤 |
| Vercel | 다크 모드 완성도, 은은한 상태 표시 |
| Linear | Optimistic UI, 키보드 우선 워크플로 |
| Raycast | 확장 시스템, 빠른 작업 |
| Stripe | 문서화 완성도, API 디자인 |
| Perplexity | Citation 우선 AI, 스트리밍 단계 |
| Obsidian | 로컬 우선 조합성, 지식 그래프 인터페이스 |
| ### Apple 네이티브 및 창작 도구 | |
| 제품 | 주요 기여 |
| --------- | ------------------ |
| Flighty | 15가지 스마트 상태, Live Activities, 데이터 시각화 |
| Halide | 지능형 활성화, 제스처 컨트롤 |
| Bear | 타이포그래피 우선, 인라인 태그 지정 |
| Craft | 네이티브 우선 크로스 플랫폼, 중첩 페이지 |
| Things | 나중 날짜 지정, 빠른 입력 패턴 |
| Darkroom | 몰입형 다크 UI, 사진 우선 편집 |
| Procreate | 제스처 우선 창작 도구 |
| Overcast | 인터페이스 디자인으로서 보이지 않는 오디오 엔지니어링 |
| Camo | 전문 영상 제작 UI |
| Ivory | 타임라인 완성도, 유쾌한 정밀함 |
| Anybox | 플랫폼 네이티브 투명성 |
| Drafts | 텍스트 우선 캡처의 명확성 |
| Notion Calendar | 캘린더 정밀성, 작업 공간 통합 |
| ### 생산성, 지식 및 금융 | |
| 제품 | 주요 기여 |
| --------- | ------------------ |
| Superhuman | 100ms 규칙, command palette 교육, 실습형 onboarding |
| Notion | block 시스템, slash command |
| Arc | Spaces, split view, command bar |
| Todoist | 따뜻한 minimalism, 최대한의 절제 |
| Amie | 즐거운 생산성, 따뜻한 minimalism |
| Loom | 친근한 async-video 전문성 |
| Pitch | 대담한 프레젠테이션 디자인 언어 |
| Readwise Reader | 깊이 읽기 도구, 우주적인 브랜딩 |
| Copilot Money | 영화적인 금융 데이터 시각화 |
| 1Password | 마찰 없는 보안 |
| Mercury | 영화적인 뱅킹의 세련미 |
| ### 소비자, 소셜 및 신뢰 | |
| Product | 주요 기여 |
| --------- | ------------------ |
| Spotify | 색상, 감정, 규모 |
| Duolingo | 디자인 언어로서의 gamification |
| Signal | 단순함을 통한 보안 |
| Airbnb | 마켓플레이스 규모의 신뢰 |
| Bluesky | 알고리즘 투명성 |
| Letterboxd | 소셜 객체로서의 영화 |
| Strava | 소셜 통화로서의 GPS 데이터 |
| Apple Music | 에디토리얼 보이스, 공간 음향 |
| Headspace | 인터페이스 전략으로서의 차분함 |
| Zomato | 개성 중심의 음식 UX |
| ### 물리적 제품, 게임 및 접근성 | |
| 제품 | 핵심 기여 |
| --------- | ------------------ |
| Balatro | 피드백 루프, 게임 감각 시스템 |
| Rivian | 모험 사진과 웅장한 타이포그래피 |
| Teenage Engineering | 제약을 미적 정체성으로 활용 |
| OKO | 오디오-햅틱 접근성 |
| CARROT Weather | 유틸리티 앱 차별화 요소로서의 개성 |
| — |
이 가이드는 실천을 통해 성장합니다. 디자인 원칙은 시대를 초월하지만, 그 적용 방식은 기술과 이해가 발전하면서 함께 진화합니다.