아이코노그래피
24px 그리드 · 1.8px 스트로크 아웃라인 아이콘 체계와 SVG → React 생성 파이프라인. 아이콘 추가는 SVG 1개 + make icons 로 끝납니다.
마지막 업데이트 2026-06-13
원칙#
WDS 아이콘은 24×24 그리드의 스트로크(아웃라인) 세트 하나로 시작합니다(ADR-011).
| 원칙 | 값 | 근거 |
|---|---|---|
| 그리드 | 24×24 viewBox 고정 | icon.size-lg(24px)와 동일 기준, 축소 렌더에도 정수 정렬 |
| 스트로크 | 1.8px (icon.stroke) | 20px 기본 렌더에서 1.5px(가늘다)와 2px(둔하다)의 중간 — Blue 잉크와 균형 |
| 캡/조인 | round | 브랜드 심볼의 둥근 기하와 동일 어휘 |
| 색 | currentColor 상속 | 아이콘 전용 색 토큰은 색 결정을 이원화하므로 금지 (ADR-011 §2) |
| Filled 세트 | 후속 | 탭바/토글 등 "선택됨" 표현이 필요해지는 시점에 추가 |
색이 없다는 것이 핵심 규칙입니다. 아이콘은 항상 부모 텍스트 색을 그대로 상속하므로,
색을 바꾸고 싶다면 부모(또는 아이콘 자신)의 color에 semantic 토큰을 지정합니다.
크기 토큰#
크기·스트로크는 semantic 계층입니다 — 여러 컴포넌트가 공유하는 "의도"이지 특정 컴포넌트의 결정이 아니기 때문입니다(ADR-011 §1).
| 토큰 | 값 | 설명 |
|---|---|---|
| 16px — 인라인/캡션 옆 (ADR-011) | ||
| 20px — 컨트롤 내 기본 | ||
| 24px — 단독/강조 (아이콘 그리드 기준) | ||
| 아웃라인 아이콘 스트로크 (24 그리드 기준) |
| 토큰 | 렌더 | 쓰임 |
|---|---|---|
icon.size-sm (16px) | 인라인 | 본문 텍스트 옆 · 캡션 · 칩 내부 |
icon.size-md (20px) | 컨트롤 기본 | 버튼·인풋·메뉴 항목 — 생성 컴포넌트의 기본값 |
icon.size-lg (24px) | 단독/강조 | 헤더 액션 · 빈 상태 · 아이콘 그리드 원본 크기 |
코어 아이콘셋#
포털 셸과 MVP 컴포넌트가 실제로 소비하는 어휘부터 시작합니다. 아래 그리드는 생성된 React 컴포넌트를 그대로 렌더한 것입니다.
- AlertTriangleIcon
- ArrowUpIcon
- BookOpenIcon
- CalendarIcon
- CheckCircleIcon
- CheckIcon
- ChevronDownIcon
- ChevronLeftIcon
- ChevronRightIcon
- CircleAlertIcon
- CloseIcon
- CopyIcon
- ExternalLinkIcon
- FileTextIcon
- GlobeIcon
- ImageIcon
- InfoIcon
- LinkIcon
- MenuIcon
- MinusIcon
- MoonIcon
- SearchIcon
- StopIcon
- SunIcon
| 컴포넌트 | 소스 | 주 용도 |
|---|---|---|
SearchIcon | search.svg | 검색(⌘K) |
CloseIcon | close.svg | 다이얼로그·칩 닫기 |
MenuIcon | menu.svg | 모바일 내비게이션 |
SunIcon / MoonIcon | sun.svg / moon.svg | 테마 토글 |
CopyIcon / CheckIcon | copy.svg / check.svg | 복사 인터랙션(복사 → 완료) |
LinkIcon | link.svg | 헤딩 앵커 |
ChevronDownIcon / ChevronRightIcon | chevron-*.svg | 아코디언·트리·브레드크럼 |
ArrowUpIcon | arrow-up.svg | AI 입력 전송 |
ExternalLinkIcon | external-link.svg | 외부 링크 표시 |
InfoIcon | info.svg | 안내 배너 |
CircleAlertIcon | circle-alert.svg | 에러 상태(경고 삼각형과 변별) |
FileTextIcon / GlobeIcon / BookOpenIcon | file-text.svg / globe.svg / book-open.svg | 지식 소스(문서·웹·위키) |
ImageIcon | image.svg | 이미지·썸네일 플레이스홀더 |
StopIcon | stop.svg | AI 응답 중단 |
렌더링 모드 (mono · hierarchical)#
생성 컴포넌트는 renderingMode prop으로 두 모드를 가집니다(Track B 5-1 Phase 1).
기본값은 mono — 현행 단색 아웃라인과 바이트 동일합니다. 즉 renderingMode를
지정하지 않은 모든 기존 사용처는 한 픽셀도 바뀌지 않습니다.
| 모드 | 색 | 깊이 | 쓰임 |
|---|---|---|---|
mono (기본) | currentColor 단색 | 없음(평면) | 본문·컨트롤·인라인 — 거의 전부 |
hierarchical | currentColor (농도만 분리) | Bloom Depth 2단 | 빈상태·강조·단독 노출에서 위계가 필요할 때 |
hierarchical는 Bloom Depth 농도 표현입니다(§0 시각 POV). 보조/컨테이너 레이어가
ICON_HIERARCHICAL_SECONDARY(0.4) 농도로 물러나고, 주체는 농도 1로 남아 한 색 안에서
깊이가 생깁니다 — SF Symbols hierarchical과 같은 어휘입니다. 두 모드 모두 currentColor를
상속하므로 색 정책(ADR-011 §2)은 그대로입니다. 명시 색(palette)·2색(duotone)은 범위 밖(후속).
레이어 주석(data-layer="2")이 없는 글리프는 두 모드에서 동일하게 렌더됩니다 — opacity가
주입되지 않습니다. 현재 4종(check-circle · info · calendar · alert-triangle)이 PoC로 보조
레이어를 가집니다 — 모두 보조 레이어가 **컨테이너/배경 프레임(원·사각·삼각)**이고 주체(체크·정보 점·
날짜 그리드·경고 "!")는 농도 1로 남는 글리프입니다. 아래는 mono(좌) ↔ hierarchical(우) 비교입니다.
- CheckCircleIcon
- InfoIcon
- CalendarIcon
- AlertTriangleIcon
- CircleAlertIcon
- FileTextIcon
- GlobeIcon
- ImageIcon
접근성 주의 — hierarchical은 장식·대형 전용. 보조 tier는
ICON_HIERARCHICAL_SECONDARY(0.4) 농도로 합성되며, currentColor가 충분히 진하지 않으면 WCAG 1.4.11(비텍스트 대비 3:1)을 보장하지 못합니다. 특히icon.size-sm(16px)에서는 0.4 농도의 가는 스트로크가 지각되지 않으므로, hierarchical은 20–24px 이상의 단독·강조·빈상태 표면에서만 쓰고 16px 인라인에는 쓰지 않습니다. 핵심 규칙: 물러나는 레이어는 반드시 컨테이너/배경 프레임(원·사각·삼각)이어야 하며, 글리프의 정체성을 이루는 주체(alert-triangle의 "!" 등)는 농도 1로 남아야 합니다 — 농도가 의미의 유일한 전달자가 되어선 안 됩니다. 프레임과 주체가 명확히 분리되지 않는 글리프(예: 대칭link)는 PoC에서 제외했습니다.
import { AlertTriangleIcon } from '@wds/ui-web';
// 기본 mono — 현행과 동일
<AlertTriangleIcon />
// hierarchical — 삼각 프레임이 농도로 물러나고 "!" 주체는 농도 1로 남음
<AlertTriangleIcon renderingMode="hierarchical" />
소스에서 보조 요소에
data-layer="2"한 줄만 부여하면 생성기가 모드 분기를 만듭니다. 이 주석은 소스 전용 — 생성 출력에선 제거되고 hierarchical에서만opacity로 변환됩니다.
옵티컬 스트로크 (설계 노트)#
작은 렌더에서 stroke가 시각적으로 가늘어지는 것을 막으려면 작은 size일수록 viewBox
stroke를 키워야 합니다(옵티컬 보정). Phase 1은 이 테이블을 설계·노출만 합니다 —
기본 컴포넌트는 여전히 ICON_STROKE(1.8px)를 그대로 쓰므로 비기본 size의 픽셀이 바뀌지
않습니다(무손상). 기본 적용은 Phase 2 결정입니다.
공식 (24 그리드가 앵커):
strokeForSize(size) = ICON_STROKE × (1 − d + d × 24 / size), d = 0.5
| 렌더 size | viewBox stroke | 비고 |
|---|---|---|
16px (icon.size-sm) | 2.25px | 가장 강한 보정 |
20px (icon.size-md) | 1.98px | 컨트롤 기본 |
24px (icon.size-lg) | 1.8px | 앵커 — ICON_STROKE 그대로 |
d = 0.5는 절반 보정입니다 — 완전 역스케일(d = 1, 렌더 px 완전 균일)은 작은 글리프를
과하게 두껍게 만들기 때문입니다. ICON_OPTICAL_STROKE 테이블과 strokeForSize(size)
헬퍼를 @wds/ui-web에서 노출합니다(소비처가 직접 적용 가능).
파이프라인 — SVG 1개 = 아이콘 1개#
아이콘 React 코드는 수기 작성 금지 — 전부 생성물입니다.
# 1. 24×24 스트로크 SVG를 icons/ 에 추가
# 2. 생성 실행
make icons
# → packages/ui-web/src/icons/<Name>Icon.tsx + index.ts 전체 재생성
생성기는 tokens/dist/manifest.json에서 icon.size-md·icon.stroke를 읽어
상수로 박습니다 — 토큰을 바꾸면 다음 make icons에서 아이콘도 함께 바뀝니다(SSOT).
소스 규격 (빌드가 강제)#
- 루트:
viewBox="0 0 24 24"·fill="none"·stroke="currentColor"— width/height 금지 - 내부:
path·circle·rect등 기하 요소만,fill·kebab 속성(stroke-width등) 금지 - 파일명:
kebab-case.svg→PascalCase + Icon(예:external-link.svg→ExternalLinkIcon)
규격 위반은 make icons가 한국어 메시지와 함께 실패시킵니다.
사용법#
import { SearchIcon, CopyIcon } from '@wds/ui-web';
// 기본 20px(icon.size-md) · 부모 색 상속
<SearchIcon />
// 크기 지정 — 토큰 3단(16/20/24) 안에서
<CopyIcon size={16} />
// 색은 부모가 결정한다
<span style={{ color: 'var(--wds-color-primary)' }}>
<SearchIcon />
</span>
접근성#
- 생성 컴포넌트는 기본
aria-hidden="true"— 아이콘은 장식이 기본값입니다. - 아이콘이 의미를 단독 전달하면(아이콘 전용 버튼 등) 컨테이너에 라벨을 부여합니다:
<button type="button" aria-label="검색">
<SearchIcon />
</button>
- 아이콘+텍스트 병기 시에는 텍스트가 이름이 되므로 추가 라벨이 필요 없습니다.
후속 (P5)#
- Dart 타겟 — 렌더 방식(아이콘 폰트/flutter_svg/CustomPaint) 결정과 함께 P5 WizTheme에서. 그 전까지 Flutter는 Material 아이콘 임시 사용 (ADR-011 §5)
- Filled 세트 — Outlined와 1:1 쌍으로 추가, "선택됨" 상태 표현용
- 앱 아이콘 — 마스터/플랫폼 파생 규칙은 04 브랜드 아이덴티티 확장으로