@kood/claude-code 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +2 -0
- package/dist/index.js +297 -0
- package/package.json +47 -0
- package/templates/hono/CLAUDE.md +376 -0
- package/templates/hono/docs/deployment/cloudflare.md +328 -0
- package/templates/hono/docs/deployment/index.md +291 -0
- package/templates/hono/docs/git/index.md +180 -0
- package/templates/hono/docs/library/hono/error-handling.md +400 -0
- package/templates/hono/docs/library/hono/index.md +241 -0
- package/templates/hono/docs/library/hono/middleware.md +334 -0
- package/templates/hono/docs/library/hono/rpc.md +454 -0
- package/templates/hono/docs/library/hono/validation.md +328 -0
- package/templates/hono/docs/library/prisma/index.md +427 -0
- package/templates/hono/docs/library/zod/index.md +413 -0
- package/templates/hono/docs/mcp/context7.md +106 -0
- package/templates/hono/docs/mcp/index.md +94 -0
- package/templates/hono/docs/mcp/sequential-thinking.md +101 -0
- package/templates/hono/docs/mcp/sgrep.md +105 -0
- package/templates/hono/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/hono/docs/skills/gemini-review/references/checklists.md +136 -0
- package/templates/hono/docs/skills/gemini-review/references/prompt-templates.md +303 -0
- package/templates/tanstack-start/CLAUDE.md +279 -0
- package/templates/tanstack-start/docs/architecture/architecture.md +547 -0
- package/templates/tanstack-start/docs/deployment/cloudflare.md +346 -0
- package/templates/tanstack-start/docs/deployment/index.md +102 -0
- package/templates/tanstack-start/docs/deployment/nitro.md +211 -0
- package/templates/tanstack-start/docs/deployment/railway.md +364 -0
- package/templates/tanstack-start/docs/deployment/vercel.md +287 -0
- package/templates/tanstack-start/docs/design/accessibility.md +433 -0
- package/templates/tanstack-start/docs/design/color.md +235 -0
- package/templates/tanstack-start/docs/design/components.md +409 -0
- package/templates/tanstack-start/docs/design/index.md +107 -0
- package/templates/tanstack-start/docs/design/safe-area.md +317 -0
- package/templates/tanstack-start/docs/design/spacing.md +341 -0
- package/templates/tanstack-start/docs/design/tailwind-setup.md +470 -0
- package/templates/tanstack-start/docs/design/typography.md +324 -0
- package/templates/tanstack-start/docs/git/index.md +203 -0
- package/templates/tanstack-start/docs/guides/best-practices.md +753 -0
- package/templates/tanstack-start/docs/guides/getting-started.md +304 -0
- package/templates/tanstack-start/docs/guides/husky-lint-staged.md +303 -0
- package/templates/tanstack-start/docs/guides/prettier.md +189 -0
- package/templates/tanstack-start/docs/guides/project-templates.md +710 -0
- package/templates/tanstack-start/docs/library/better-auth/2fa.md +136 -0
- package/templates/tanstack-start/docs/library/better-auth/advanced.md +138 -0
- package/templates/tanstack-start/docs/library/better-auth/index.md +83 -0
- package/templates/tanstack-start/docs/library/better-auth/plugins.md +111 -0
- package/templates/tanstack-start/docs/library/better-auth/session.md +127 -0
- package/templates/tanstack-start/docs/library/better-auth/setup.md +123 -0
- package/templates/tanstack-start/docs/library/prisma/crud.md +218 -0
- package/templates/tanstack-start/docs/library/prisma/index.md +165 -0
- package/templates/tanstack-start/docs/library/prisma/relations.md +191 -0
- package/templates/tanstack-start/docs/library/prisma/schema.md +177 -0
- package/templates/tanstack-start/docs/library/prisma/setup.md +156 -0
- package/templates/tanstack-start/docs/library/prisma/transactions.md +140 -0
- package/templates/tanstack-start/docs/library/tanstack-query/index.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/invalidation.md +146 -0
- package/templates/tanstack-start/docs/library/tanstack-query/optimistic-updates.md +196 -0
- package/templates/tanstack-start/docs/library/tanstack-query/setup.md +110 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-mutation.md +170 -0
- package/templates/tanstack-start/docs/library/tanstack-query/use-query.md +173 -0
- package/templates/tanstack-start/docs/library/tanstack-start/auth-patterns.md +171 -0
- package/templates/tanstack-start/docs/library/tanstack-start/index.md +114 -0
- package/templates/tanstack-start/docs/library/tanstack-start/middleware.md +142 -0
- package/templates/tanstack-start/docs/library/tanstack-start/routing.md +163 -0
- package/templates/tanstack-start/docs/library/tanstack-start/server-functions.md +128 -0
- package/templates/tanstack-start/docs/library/tanstack-start/setup.md +85 -0
- package/templates/tanstack-start/docs/library/zod/basic-types.md +186 -0
- package/templates/tanstack-start/docs/library/zod/complex-types.md +204 -0
- package/templates/tanstack-start/docs/library/zod/index.md +186 -0
- package/templates/tanstack-start/docs/library/zod/transforms.md +174 -0
- package/templates/tanstack-start/docs/library/zod/validation.md +208 -0
- package/templates/tanstack-start/docs/mcp/context7.md +204 -0
- package/templates/tanstack-start/docs/mcp/index.md +116 -0
- package/templates/tanstack-start/docs/mcp/sequential-thinking.md +180 -0
- package/templates/tanstack-start/docs/mcp/sgrep.md +174 -0
- package/templates/tanstack-start/docs/skills/gemini-review/SKILL.md +220 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/checklists.md +150 -0
- package/templates/tanstack-start/docs/skills/gemini-review/references/prompt-templates.md +293 -0
|
@@ -0,0 +1,433 @@
|
|
|
1
|
+
# 접근성 (Accessibility)
|
|
2
|
+
|
|
3
|
+
> **상위 문서**: [UI/UX 디자인 가이드](./index.md)
|
|
4
|
+
|
|
5
|
+
접근성(A11y)은 장애가 있는 사용자도 웹사이트를 사용할 수 있게 만드는 것입니다. 이는 윤리적 의무이자 법적 요구사항이기도 합니다.
|
|
6
|
+
|
|
7
|
+
## 왜 접근성이 중요한가?
|
|
8
|
+
|
|
9
|
+
### 접근성이 필요한 사용자
|
|
10
|
+
|
|
11
|
+
| 유형 | 예시 | 필요한 지원 |
|
|
12
|
+
|------|------|------------|
|
|
13
|
+
| **시각 장애** | 전맹, 저시력, 색맹 | 스크린 리더, 고대비, 큰 텍스트 |
|
|
14
|
+
| **청각 장애** | 난청, 전농 | 자막, 시각적 알림 |
|
|
15
|
+
| **운동 장애** | 손떨림, 마비 | 키보드 네비게이션, 큰 클릭 영역 |
|
|
16
|
+
| **인지 장애** | 읽기 장애, 집중력 장애 | 단순한 언어, 명확한 구조 |
|
|
17
|
+
|
|
18
|
+
### 접근성의 이점
|
|
19
|
+
|
|
20
|
+
- 법적 준수 (ADA, 장애인차별금지법)
|
|
21
|
+
- 더 넓은 사용자층
|
|
22
|
+
- SEO 개선 (검색 엔진도 접근성 기준 활용)
|
|
23
|
+
- 전반적인 UX 향상 (모든 사용자에게 도움)
|
|
24
|
+
|
|
25
|
+
## WCAG 기준
|
|
26
|
+
|
|
27
|
+
WCAG(Web Content Accessibility Guidelines)는 웹 접근성의 국제 표준입니다.
|
|
28
|
+
|
|
29
|
+
### 준수 수준
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
Level A - 최소 기준 (필수)
|
|
33
|
+
Level AA - 권장 기준 (일반적 목표)
|
|
34
|
+
Level AAA - 최고 기준 (특수한 경우)
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 4가지 원칙 (POUR)
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
P - Perceivable (인식 가능) 모든 정보를 인식할 수 있어야 함
|
|
41
|
+
O - Operable (조작 가능) 모든 기능을 조작할 수 있어야 함
|
|
42
|
+
U - Understandable (이해 가능) 내용을 이해할 수 있어야 함
|
|
43
|
+
R - Robust (견고함) 다양한 기술에서 동작해야 함
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## 색상 접근성
|
|
47
|
+
|
|
48
|
+
### 색상 대비 요구사항
|
|
49
|
+
|
|
50
|
+
| 텍스트 유형 | 최소 대비 (AA) | 권장 대비 (AAA) |
|
|
51
|
+
|------------|---------------|----------------|
|
|
52
|
+
| 일반 텍스트 (< 18px) | 4.5:1 | 7:1 |
|
|
53
|
+
| 큰 텍스트 (≥ 18px bold, ≥ 24px) | 3:1 | 4.5:1 |
|
|
54
|
+
| UI 컴포넌트, 그래픽 | 3:1 | - |
|
|
55
|
+
|
|
56
|
+
### 안전한 색상 조합
|
|
57
|
+
|
|
58
|
+
```tsx
|
|
59
|
+
// ✅ 좋은 대비
|
|
60
|
+
<p className="text-gray-900 bg-white">검정 on 흰색 (21:1)</p>
|
|
61
|
+
<p className="text-white bg-blue-700">흰색 on 진한 파랑 (8.6:1)</p>
|
|
62
|
+
<p className="text-gray-700 bg-gray-100">진한 회색 on 밝은 회색 (5.4:1)</p>
|
|
63
|
+
|
|
64
|
+
// ❌ 나쁜 대비
|
|
65
|
+
<p className="text-gray-400 bg-white">밝은 회색 on 흰색 (2.7:1)</p>
|
|
66
|
+
<p className="text-yellow-500 bg-white">노란색 on 흰색 (1.3:1)</p>
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### 색상만으로 정보 전달 금지
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// ❌ 나쁜 예 - 색상만으로 구분
|
|
73
|
+
<span className="text-red-600">오류</span>
|
|
74
|
+
<span className="text-green-600">성공</span>
|
|
75
|
+
|
|
76
|
+
// ✅ 좋은 예 - 아이콘/텍스트 병행
|
|
77
|
+
<span className="text-red-600 flex items-center gap-1">
|
|
78
|
+
<XCircleIcon className="w-4 h-4" />
|
|
79
|
+
오류가 발생했습니다
|
|
80
|
+
</span>
|
|
81
|
+
<span className="text-green-600 flex items-center gap-1">
|
|
82
|
+
<CheckCircleIcon className="w-4 h-4" />
|
|
83
|
+
성공적으로 저장됨
|
|
84
|
+
</span>
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## 키보드 접근성
|
|
88
|
+
|
|
89
|
+
### 모든 기능은 키보드로 가능해야 함
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Tab 다음 요소로 이동
|
|
93
|
+
Shift+Tab 이전 요소로 이동
|
|
94
|
+
Enter 버튼 클릭, 링크 이동
|
|
95
|
+
Space 체크박스 토글, 버튼 클릭
|
|
96
|
+
Escape 모달 닫기, 취소
|
|
97
|
+
Arrow 드롭다운, 슬라이더 조작
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
### 포커스 표시
|
|
101
|
+
|
|
102
|
+
```tsx
|
|
103
|
+
// ✅ 명확한 포커스 스타일
|
|
104
|
+
<button className="focus:outline-none focus:ring-2 focus:ring-blue-500
|
|
105
|
+
focus:ring-offset-2">
|
|
106
|
+
버튼
|
|
107
|
+
</button>
|
|
108
|
+
|
|
109
|
+
// 입력 필드
|
|
110
|
+
<input className="focus:outline-none focus:ring-2 focus:ring-blue-500
|
|
111
|
+
focus:border-blue-500" />
|
|
112
|
+
|
|
113
|
+
// ❌ 포커스 제거 금지
|
|
114
|
+
<button className="outline-none focus:outline-none">
|
|
115
|
+
{/* 접근성 위반! */}
|
|
116
|
+
</button>
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### 포커스 순서
|
|
120
|
+
|
|
121
|
+
```tsx
|
|
122
|
+
// 논리적인 탭 순서 유지
|
|
123
|
+
<form>
|
|
124
|
+
<input tabIndex={0} /> {/* 1번째 */}
|
|
125
|
+
<input tabIndex={0} /> {/* 2번째 */}
|
|
126
|
+
<button tabIndex={0}>취소</button> {/* 3번째 */}
|
|
127
|
+
<button tabIndex={0}>저장</button> {/* 4번째 */}
|
|
128
|
+
</form>
|
|
129
|
+
|
|
130
|
+
// ❌ tabIndex로 순서 강제 변경 피하기
|
|
131
|
+
<button tabIndex={2}>저장</button>
|
|
132
|
+
<button tabIndex={1}>취소</button>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### 포커스 트랩 (모달)
|
|
136
|
+
|
|
137
|
+
```tsx
|
|
138
|
+
// 모달이 열리면 포커스가 모달 안에만 머물러야 함
|
|
139
|
+
const Modal = ({ isOpen, onClose }) => {
|
|
140
|
+
const modalRef = useRef(null)
|
|
141
|
+
|
|
142
|
+
useEffect(() => {
|
|
143
|
+
if (isOpen) {
|
|
144
|
+
// 모달 열릴 때 첫 번째 요소에 포커스
|
|
145
|
+
modalRef.current?.querySelector('button')?.focus()
|
|
146
|
+
}
|
|
147
|
+
}, [isOpen])
|
|
148
|
+
|
|
149
|
+
const handleKeyDown = (e) => {
|
|
150
|
+
if (e.key === 'Escape') onClose()
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
return (
|
|
154
|
+
<div
|
|
155
|
+
ref={modalRef}
|
|
156
|
+
role="dialog"
|
|
157
|
+
aria-modal="true"
|
|
158
|
+
onKeyDown={handleKeyDown}
|
|
159
|
+
>
|
|
160
|
+
{/* 모달 내용 */}
|
|
161
|
+
</div>
|
|
162
|
+
)
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## 시맨틱 HTML
|
|
167
|
+
|
|
168
|
+
### 올바른 HTML 요소 사용
|
|
169
|
+
|
|
170
|
+
```tsx
|
|
171
|
+
// ✅ 시맨틱 HTML
|
|
172
|
+
<header>...</header>
|
|
173
|
+
<nav>...</nav>
|
|
174
|
+
<main>
|
|
175
|
+
<article>
|
|
176
|
+
<h1>제목</h1>
|
|
177
|
+
<section>...</section>
|
|
178
|
+
</article>
|
|
179
|
+
</main>
|
|
180
|
+
<footer>...</footer>
|
|
181
|
+
|
|
182
|
+
// ❌ div 남용
|
|
183
|
+
<div className="header">...</div>
|
|
184
|
+
<div className="nav">...</div>
|
|
185
|
+
<div className="main">...</div>
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### 제목 계층
|
|
189
|
+
|
|
190
|
+
```tsx
|
|
191
|
+
// ✅ 올바른 제목 순서
|
|
192
|
+
<h1>페이지 제목</h1>
|
|
193
|
+
<h2>섹션 1</h2>
|
|
194
|
+
<h3>하위 섹션 1-1</h3>
|
|
195
|
+
<h2>섹션 2</h2>
|
|
196
|
+
|
|
197
|
+
// ❌ 제목 레벨 건너뛰기
|
|
198
|
+
<h1>페이지 제목</h1>
|
|
199
|
+
<h3>섹션</h3> {/* h2를 건너뜀 */}
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
### 버튼 vs 링크
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
// 버튼: 동작 수행
|
|
206
|
+
<button onClick={handleSave}>저장</button>
|
|
207
|
+
|
|
208
|
+
// 링크: 페이지 이동
|
|
209
|
+
<a href="/about">소개 페이지</a>
|
|
210
|
+
|
|
211
|
+
// ❌ 잘못된 사용
|
|
212
|
+
<div onClick={handleSave}>저장</div> {/* div를 버튼처럼 */}
|
|
213
|
+
<a onClick={handleAction}>클릭</a> {/* 링크를 버튼처럼 */}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
## ARIA 속성
|
|
217
|
+
|
|
218
|
+
### 필수 ARIA 사용
|
|
219
|
+
|
|
220
|
+
```tsx
|
|
221
|
+
// 레이블 연결
|
|
222
|
+
<label htmlFor="email">이메일</label>
|
|
223
|
+
<input id="email" type="email" />
|
|
224
|
+
|
|
225
|
+
// 또는 aria-label 사용
|
|
226
|
+
<input aria-label="검색어 입력" type="search" />
|
|
227
|
+
|
|
228
|
+
// 설명 연결
|
|
229
|
+
<input aria-describedby="email-hint" />
|
|
230
|
+
<p id="email-hint">업무용 이메일을 입력하세요</p>
|
|
231
|
+
|
|
232
|
+
// 에러 상태
|
|
233
|
+
<input aria-invalid="true" aria-describedby="email-error" />
|
|
234
|
+
<p id="email-error" role="alert">올바른 이메일을 입력하세요</p>
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
### 상태 알림
|
|
238
|
+
|
|
239
|
+
```tsx
|
|
240
|
+
// 동적 콘텐츠 변경 알림
|
|
241
|
+
<div aria-live="polite">
|
|
242
|
+
{/* 변경되면 스크린 리더가 읽어줌 */}
|
|
243
|
+
{message}
|
|
244
|
+
</div>
|
|
245
|
+
|
|
246
|
+
// 긴급 알림
|
|
247
|
+
<div aria-live="assertive" role="alert">
|
|
248
|
+
{errorMessage}
|
|
249
|
+
</div>
|
|
250
|
+
```
|
|
251
|
+
|
|
252
|
+
### 숨김 처리
|
|
253
|
+
|
|
254
|
+
```tsx
|
|
255
|
+
// 시각적으로만 숨김 (스크린 리더는 읽음)
|
|
256
|
+
<span className="sr-only">메뉴 열기</span>
|
|
257
|
+
|
|
258
|
+
// Tailwind의 sr-only 클래스
|
|
259
|
+
.sr-only {
|
|
260
|
+
position: absolute;
|
|
261
|
+
width: 1px;
|
|
262
|
+
height: 1px;
|
|
263
|
+
padding: 0;
|
|
264
|
+
margin: -1px;
|
|
265
|
+
overflow: hidden;
|
|
266
|
+
clip: rect(0, 0, 0, 0);
|
|
267
|
+
border: 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
// 스크린 리더에서도 숨김
|
|
271
|
+
<div aria-hidden="true">장식용 아이콘</div>
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## 폼 접근성
|
|
275
|
+
|
|
276
|
+
### 레이블 필수
|
|
277
|
+
|
|
278
|
+
```tsx
|
|
279
|
+
// ✅ 명시적 레이블
|
|
280
|
+
<label htmlFor="name">이름</label>
|
|
281
|
+
<input id="name" type="text" />
|
|
282
|
+
|
|
283
|
+
// ✅ 암묵적 레이블
|
|
284
|
+
<label>
|
|
285
|
+
이름
|
|
286
|
+
<input type="text" />
|
|
287
|
+
</label>
|
|
288
|
+
|
|
289
|
+
// ✅ aria-label (시각적 레이블 없을 때)
|
|
290
|
+
<input type="search" aria-label="사이트 검색" />
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
### 에러 처리
|
|
294
|
+
|
|
295
|
+
```tsx
|
|
296
|
+
<div>
|
|
297
|
+
<label htmlFor="email">이메일</label>
|
|
298
|
+
<input
|
|
299
|
+
id="email"
|
|
300
|
+
type="email"
|
|
301
|
+
aria-invalid={hasError}
|
|
302
|
+
aria-describedby={hasError ? "email-error" : undefined}
|
|
303
|
+
className={hasError ? "border-red-500" : "border-gray-300"}
|
|
304
|
+
/>
|
|
305
|
+
{hasError && (
|
|
306
|
+
<p id="email-error" className="text-red-600 text-sm mt-1" role="alert">
|
|
307
|
+
올바른 이메일 형식을 입력하세요
|
|
308
|
+
</p>
|
|
309
|
+
)}
|
|
310
|
+
</div>
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
### 필수 필드 표시
|
|
314
|
+
|
|
315
|
+
```tsx
|
|
316
|
+
<label htmlFor="email">
|
|
317
|
+
이메일 <span className="text-red-600" aria-hidden="true">*</span>
|
|
318
|
+
<span className="sr-only">(필수)</span>
|
|
319
|
+
</label>
|
|
320
|
+
<input id="email" required aria-required="true" />
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
## 이미지 접근성
|
|
324
|
+
|
|
325
|
+
### 대체 텍스트 (alt)
|
|
326
|
+
|
|
327
|
+
```tsx
|
|
328
|
+
// 정보 전달 이미지 - 설명 필요
|
|
329
|
+
<img src="chart.png" alt="2024년 매출 추이: 1월 100만원에서 12월 500만원으로 성장" />
|
|
330
|
+
|
|
331
|
+
// 장식용 이미지 - 빈 alt
|
|
332
|
+
<img src="decorative.png" alt="" />
|
|
333
|
+
|
|
334
|
+
// 아이콘 버튼 - 기능 설명
|
|
335
|
+
<button aria-label="닫기">
|
|
336
|
+
<img src="close.svg" alt="" />
|
|
337
|
+
</button>
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
## 터치 접근성
|
|
341
|
+
|
|
342
|
+
### 최소 터치 영역
|
|
343
|
+
|
|
344
|
+
```tsx
|
|
345
|
+
// 최소 44x44px 터치 영역
|
|
346
|
+
<button className="min-w-[44px] min-h-[44px] p-2">
|
|
347
|
+
<Icon className="w-6 h-6" />
|
|
348
|
+
</button>
|
|
349
|
+
|
|
350
|
+
// 작은 체크박스도 터치 영역 확보
|
|
351
|
+
<label className="flex items-center gap-2 p-2 -m-2 cursor-pointer">
|
|
352
|
+
<input type="checkbox" className="w-4 h-4" />
|
|
353
|
+
<span>동의합니다</span>
|
|
354
|
+
</label>
|
|
355
|
+
```
|
|
356
|
+
|
|
357
|
+
### 터치 타겟 간격
|
|
358
|
+
|
|
359
|
+
```tsx
|
|
360
|
+
// 터치 타겟 사이 최소 8px 간격
|
|
361
|
+
<div className="flex gap-2">
|
|
362
|
+
<button className="p-3">버튼 1</button>
|
|
363
|
+
<button className="p-3">버튼 2</button>
|
|
364
|
+
</div>
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## 접근성 테스트
|
|
368
|
+
|
|
369
|
+
### 자동화 도구
|
|
370
|
+
|
|
371
|
+
| 도구 | 용도 |
|
|
372
|
+
|------|------|
|
|
373
|
+
| **axe DevTools** | 브라우저 확장, 자동 검사 |
|
|
374
|
+
| **Lighthouse** | Chrome 내장, 접근성 점수 |
|
|
375
|
+
| **WAVE** | 시각적 피드백 |
|
|
376
|
+
| **eslint-plugin-jsx-a11y** | 코드 레벨 검사 |
|
|
377
|
+
|
|
378
|
+
### 수동 테스트
|
|
379
|
+
|
|
380
|
+
```
|
|
381
|
+
1. 키보드만으로 모든 기능 사용 가능한가?
|
|
382
|
+
2. Tab 순서가 논리적인가?
|
|
383
|
+
3. 포커스 표시가 명확한가?
|
|
384
|
+
4. 스크린 리더로 내용을 이해할 수 있는가?
|
|
385
|
+
5. 200% 확대해도 사용 가능한가?
|
|
386
|
+
```
|
|
387
|
+
|
|
388
|
+
### ESLint 설정
|
|
389
|
+
|
|
390
|
+
```bash
|
|
391
|
+
yarn add -D eslint-plugin-jsx-a11y
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
```js
|
|
395
|
+
// eslint.config.js
|
|
396
|
+
import jsxA11y from 'eslint-plugin-jsx-a11y'
|
|
397
|
+
|
|
398
|
+
export default [
|
|
399
|
+
{
|
|
400
|
+
plugins: { 'jsx-a11y': jsxA11y },
|
|
401
|
+
rules: {
|
|
402
|
+
'jsx-a11y/alt-text': 'error',
|
|
403
|
+
'jsx-a11y/anchor-has-content': 'error',
|
|
404
|
+
'jsx-a11y/click-events-have-key-events': 'error',
|
|
405
|
+
'jsx-a11y/no-noninteractive-element-interactions': 'error',
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
]
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
## 체크리스트
|
|
412
|
+
|
|
413
|
+
### 필수 (Level A)
|
|
414
|
+
|
|
415
|
+
- [ ] 모든 이미지에 alt 텍스트
|
|
416
|
+
- [ ] 모든 폼 요소에 레이블
|
|
417
|
+
- [ ] 키보드로 모든 기능 접근 가능
|
|
418
|
+
- [ ] 포커스 표시 유지
|
|
419
|
+
- [ ] 색상만으로 정보 전달하지 않음
|
|
420
|
+
|
|
421
|
+
### 권장 (Level AA)
|
|
422
|
+
|
|
423
|
+
- [ ] 텍스트 대비 4.5:1 이상
|
|
424
|
+
- [ ] 페이지 제목 명확
|
|
425
|
+
- [ ] 제목 계층 논리적
|
|
426
|
+
- [ ] 에러 메시지 명확
|
|
427
|
+
- [ ] 200% 확대 시 가로 스크롤 없음
|
|
428
|
+
|
|
429
|
+
### 최고 수준 (Level AAA)
|
|
430
|
+
|
|
431
|
+
- [ ] 텍스트 대비 7:1 이상
|
|
432
|
+
- [ ] 읽기 수준 고려
|
|
433
|
+
- [ ] 약어 설명 제공
|
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# 색상 시스템
|
|
2
|
+
|
|
3
|
+
> **상위 문서**: [UI/UX 디자인 가이드](./index.md)
|
|
4
|
+
|
|
5
|
+
색상은 UI에서 가장 강력한 시각적 요소입니다. 일관된 색상 사용은 브랜드 인지도를 높이고 사용자 경험을 개선합니다.
|
|
6
|
+
|
|
7
|
+
## 60-30-10 규칙
|
|
8
|
+
|
|
9
|
+
디자이너들이 가장 많이 사용하는 색상 배분 규칙입니다.
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
┌─────────────────────────────────────────────────┐
|
|
13
|
+
│ │
|
|
14
|
+
│ 60% 기본색 (Primary) │
|
|
15
|
+
│ 배경, 여백, 기본 영역 │
|
|
16
|
+
│ │
|
|
17
|
+
├───────────────────────────────────┬─────────────┤
|
|
18
|
+
│ │ │
|
|
19
|
+
│ 30% 보조색 (Secondary) │ 10% │
|
|
20
|
+
│ 카드, 섹션, 구분 영역 │ 강조색 │
|
|
21
|
+
│ │ (Accent) │
|
|
22
|
+
│ │ 버튼, │
|
|
23
|
+
│ │ 링크, │
|
|
24
|
+
│ │ CTA │
|
|
25
|
+
└───────────────────────────────────┴─────────────┘
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
### 실제 적용 예시
|
|
29
|
+
|
|
30
|
+
```tsx
|
|
31
|
+
// 60% - 배경
|
|
32
|
+
<div className="bg-white"> {/* 흰색 배경 */}
|
|
33
|
+
|
|
34
|
+
// 30% - 보조 영역
|
|
35
|
+
<section className="bg-gray-50"> {/* 밝은 회색 섹션 */}
|
|
36
|
+
<div className="bg-white"> {/* 카드 배경 */}
|
|
37
|
+
|
|
38
|
+
// 10% - 강조
|
|
39
|
+
<button className="bg-blue-600"> {/* 주요 버튼 */}
|
|
40
|
+
시작하기
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</section>
|
|
44
|
+
</div>
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## 색상 팔레트 구성
|
|
48
|
+
|
|
49
|
+
### 1. 기본 색상 (Neutral Colors)
|
|
50
|
+
|
|
51
|
+
배경, 텍스트, 테두리에 사용하는 중립적인 색상입니다.
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
White #FFFFFF bg-white 가장 밝은 배경
|
|
55
|
+
Gray 50 #F9FAFB bg-gray-50 섹션 배경
|
|
56
|
+
Gray 100 #F3F4F6 bg-gray-100 카드 배경, hover
|
|
57
|
+
Gray 200 #E5E7EB bg-gray-200 테두리, 구분선
|
|
58
|
+
Gray 300 #D1D5DB bg-gray-300 비활성 테두리
|
|
59
|
+
Gray 400 #9CA3AF text-gray-400 비활성 텍스트
|
|
60
|
+
Gray 500 #6B7280 text-gray-500 보조 텍스트
|
|
61
|
+
Gray 600 #4B5563 text-gray-600 부제목
|
|
62
|
+
Gray 700 #374151 text-gray-700 본문
|
|
63
|
+
Gray 800 #1F2937 text-gray-800 제목
|
|
64
|
+
Gray 900 #111827 text-gray-900 강조 텍스트
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
### 2. 브랜드 색상 (Brand Colors)
|
|
68
|
+
|
|
69
|
+
서비스를 대표하는 주요 색상입니다. **1-2가지**만 선택하세요.
|
|
70
|
+
|
|
71
|
+
```tsx
|
|
72
|
+
// 예시: 파란색 브랜드
|
|
73
|
+
const brandColors = {
|
|
74
|
+
primary: {
|
|
75
|
+
50: '#EFF6FF', // 매우 밝은 배경
|
|
76
|
+
100: '#DBEAFE', // 밝은 배경
|
|
77
|
+
200: '#BFDBFE', // hover 배경
|
|
78
|
+
300: '#93C5FD', // 테두리
|
|
79
|
+
400: '#60A5FA', // 비활성 버튼
|
|
80
|
+
500: '#3B82F6', // 기본 버튼
|
|
81
|
+
600: '#2563EB', // hover 버튼
|
|
82
|
+
700: '#1D4ED8', // active 버튼
|
|
83
|
+
800: '#1E40AF', // 어두운 버튼
|
|
84
|
+
900: '#1E3A8A', // 매우 어두운
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 3. 의미론적 색상 (Semantic Colors)
|
|
90
|
+
|
|
91
|
+
특정 의미를 가진 색상입니다. **절대 다른 용도로 사용하지 마세요.**
|
|
92
|
+
|
|
93
|
+
| 색상 | 의미 | 사용처 | Tailwind |
|
|
94
|
+
|------|------|--------|----------|
|
|
95
|
+
| 🟢 Green | 성공, 완료 | 성공 메시지, 체크 | `text-green-600` |
|
|
96
|
+
| 🔴 Red | 오류, 위험 | 에러 메시지, 삭제 | `text-red-600` |
|
|
97
|
+
| 🟡 Yellow | 경고, 주의 | 경고 메시지 | `text-yellow-600` |
|
|
98
|
+
| 🔵 Blue | 정보, 안내 | 안내 메시지, 링크 | `text-blue-600` |
|
|
99
|
+
|
|
100
|
+
```tsx
|
|
101
|
+
// ✅ 올바른 사용
|
|
102
|
+
<span className="text-green-600">저장되었습니다</span>
|
|
103
|
+
<span className="text-red-600">필수 항목입니다</span>
|
|
104
|
+
|
|
105
|
+
// ❌ 잘못된 사용 - 빨간색을 장식용으로
|
|
106
|
+
<span className="text-red-600">새로운 기능!</span>
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## 색상 대비 (Contrast)
|
|
110
|
+
|
|
111
|
+
### WCAG 접근성 기준
|
|
112
|
+
|
|
113
|
+
텍스트가 잘 읽히려면 배경과 충분한 대비가 필요합니다.
|
|
114
|
+
|
|
115
|
+
| 기준 | 최소 대비 | 적용 대상 |
|
|
116
|
+
|------|----------|----------|
|
|
117
|
+
| AA (일반) | 4.5:1 | 일반 텍스트 (16px 미만) |
|
|
118
|
+
| AA (큰 텍스트) | 3:1 | 큰 텍스트 (18px 이상) |
|
|
119
|
+
| AAA (강화) | 7:1 | 최고 수준 접근성 |
|
|
120
|
+
|
|
121
|
+
### 안전한 조합
|
|
122
|
+
|
|
123
|
+
```tsx
|
|
124
|
+
// ✅ 좋은 대비 (흰 배경)
|
|
125
|
+
<p className="text-gray-900">진한 텍스트 - 대비 15.8:1</p>
|
|
126
|
+
<p className="text-gray-700">본문 텍스트 - 대비 8.6:1</p>
|
|
127
|
+
<p className="text-gray-600">보조 텍스트 - 대비 5.7:1</p>
|
|
128
|
+
|
|
129
|
+
// ⚠️ 주의 필요
|
|
130
|
+
<p className="text-gray-500">밝은 텍스트 - 대비 4.6:1 (경계)</p>
|
|
131
|
+
|
|
132
|
+
// ❌ 피해야 함
|
|
133
|
+
<p className="text-gray-400">너무 밝음 - 대비 3.0:1</p>
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### 버튼 색상 대비
|
|
137
|
+
|
|
138
|
+
```tsx
|
|
139
|
+
// ✅ 좋은 버튼 대비
|
|
140
|
+
<button className="bg-blue-600 text-white"> {/* 흰색 on 파란색 */}
|
|
141
|
+
<button className="bg-gray-900 text-white"> {/* 흰색 on 검정색 */}
|
|
142
|
+
<button className="bg-white text-gray-900 border"> {/* 검정색 on 흰색 */}
|
|
143
|
+
|
|
144
|
+
// ❌ 나쁜 버튼 대비
|
|
145
|
+
<button className="bg-yellow-300 text-white"> {/* 대비 부족 */}
|
|
146
|
+
<button className="bg-gray-200 text-gray-400"> {/* 대비 부족 */}
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
## 다크 모드
|
|
150
|
+
|
|
151
|
+
다크 모드는 선택이 아닌 필수가 되어가고 있습니다.
|
|
152
|
+
|
|
153
|
+
### 색상 반전 원칙
|
|
154
|
+
|
|
155
|
+
```
|
|
156
|
+
라이트 모드 다크 모드
|
|
157
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
158
|
+
bg-white ←→ bg-gray-900
|
|
159
|
+
bg-gray-50 ←→ bg-gray-800
|
|
160
|
+
bg-gray-100 ←→ bg-gray-700
|
|
161
|
+
text-gray-900 ←→ text-white
|
|
162
|
+
text-gray-700 ←→ text-gray-200
|
|
163
|
+
text-gray-500 ←→ text-gray-400
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### Tailwind 다크 모드 적용
|
|
167
|
+
|
|
168
|
+
```tsx
|
|
169
|
+
// 기본 패턴
|
|
170
|
+
<div className="bg-white dark:bg-gray-900">
|
|
171
|
+
<h1 className="text-gray-900 dark:text-white">제목</h1>
|
|
172
|
+
<p className="text-gray-600 dark:text-gray-300">본문</p>
|
|
173
|
+
</div>
|
|
174
|
+
|
|
175
|
+
// 카드 컴포넌트
|
|
176
|
+
<div className="bg-white dark:bg-gray-800
|
|
177
|
+
border border-gray-200 dark:border-gray-700
|
|
178
|
+
shadow-sm">
|
|
179
|
+
<h3 className="text-gray-900 dark:text-white">카드 제목</h3>
|
|
180
|
+
</div>
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
## 색상 사용 체크리스트
|
|
184
|
+
|
|
185
|
+
### 반드시 지켜야 할 것
|
|
186
|
+
|
|
187
|
+
- [ ] 브랜드 색상은 1-2가지만 사용
|
|
188
|
+
- [ ] 의미론적 색상(빨강, 초록 등)은 해당 의미로만 사용
|
|
189
|
+
- [ ] 텍스트-배경 대비 4.5:1 이상 유지
|
|
190
|
+
- [ ] 색상만으로 정보 전달하지 않기 (아이콘/텍스트 병행)
|
|
191
|
+
|
|
192
|
+
### 권장 사항
|
|
193
|
+
|
|
194
|
+
- [ ] 60-30-10 규칙 따르기
|
|
195
|
+
- [ ] 다크 모드 지원
|
|
196
|
+
- [ ] 색상 변수(토큰) 사용
|
|
197
|
+
- [ ] 일관된 색상 네이밍
|
|
198
|
+
|
|
199
|
+
## Tailwind 설정
|
|
200
|
+
|
|
201
|
+
```css
|
|
202
|
+
/* src/styles/app.css */
|
|
203
|
+
@import "tailwindcss";
|
|
204
|
+
|
|
205
|
+
@theme {
|
|
206
|
+
/* 브랜드 색상 */
|
|
207
|
+
--color-primary-50: oklch(0.97 0.01 250);
|
|
208
|
+
--color-primary-100: oklch(0.93 0.03 250);
|
|
209
|
+
--color-primary-500: oklch(0.55 0.2 250);
|
|
210
|
+
--color-primary-600: oklch(0.48 0.22 250);
|
|
211
|
+
--color-primary-700: oklch(0.42 0.2 250);
|
|
212
|
+
|
|
213
|
+
/* 의미론적 색상 */
|
|
214
|
+
--color-success: oklch(0.55 0.15 145);
|
|
215
|
+
--color-error: oklch(0.55 0.2 25);
|
|
216
|
+
--color-warning: oklch(0.75 0.15 85);
|
|
217
|
+
--color-info: oklch(0.55 0.2 250);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
```tsx
|
|
222
|
+
// 사용 예시
|
|
223
|
+
<button className="bg-primary-600 hover:bg-primary-700">
|
|
224
|
+
주요 버튼
|
|
225
|
+
</button>
|
|
226
|
+
|
|
227
|
+
<span className="text-success">성공!</span>
|
|
228
|
+
<span className="text-error">오류 발생</span>
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## 참고 자료
|
|
232
|
+
|
|
233
|
+
- [Tailwind CSS Colors](https://tailwindcss.com/docs/colors)
|
|
234
|
+
- [WCAG Color Contrast](https://www.w3.org/WAI/WCAG21/Understanding/contrast-minimum.html)
|
|
235
|
+
- [Coolors - 색상 팔레트 생성](https://coolors.co/)
|