IconographyP3 본문

아이코노그래피

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
컴포넌트소스주 용도
SearchIconsearch.svg검색(⌘K)
CloseIconclose.svg다이얼로그·칩 닫기
MenuIconmenu.svg모바일 내비게이션
SunIcon / MoonIconsun.svg / moon.svg테마 토글
CopyIcon / CheckIconcopy.svg / check.svg복사 인터랙션(복사 → 완료)
LinkIconlink.svg헤딩 앵커
ChevronDownIcon / ChevronRightIconchevron-*.svg아코디언·트리·브레드크럼
ArrowUpIconarrow-up.svgAI 입력 전송
ExternalLinkIconexternal-link.svg외부 링크 표시
InfoIconinfo.svg안내 배너
CircleAlertIconcircle-alert.svg에러 상태(경고 삼각형과 변별)
FileTextIcon / GlobeIcon / BookOpenIconfile-text.svg / globe.svg / book-open.svg지식 소스(문서·웹·위키)
ImageIconimage.svg이미지·썸네일 플레이스홀더
StopIconstop.svgAI 응답 중단

렌더링 모드 (mono · hierarchical)#

생성 컴포넌트는 renderingMode prop으로 두 모드를 가집니다(Track B 5-1 Phase 1). 기본값은 mono — 현행 단색 아웃라인과 바이트 동일합니다. 즉 renderingMode를 지정하지 않은 모든 기존 사용처는 한 픽셀도 바뀌지 않습니다.

모드깊이쓰임
mono (기본)currentColor 단색없음(평면)본문·컨트롤·인라인 — 거의 전부
hierarchicalcurrentColor (농도만 분리)Bloom Depth 2단빈상태·강조·단독 노출에서 위계가 필요할 때

hierarchicalBloom 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
렌더 sizeviewBox 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.svgPascalCase + Icon (예: external-link.svgExternalLinkIcon)

규격 위반은 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 브랜드 아이덴티티 확장으로