Files
Pier_v1/RAG/design system_v0.1.1/design.medium.screen.md
T
ahnyongjooandClaude Sonnet 5.5 964a841de1 chore(rebar): Pier_T_rebar 작업 폴더 초기 구성 및 외부 의존 파일 통합
- Code\rebar_pipeline, review_pipeline, pier-program을 정션에서 실제 폴더로 전환
- Code\rhino_mcp 및 CLAUDE_rhino배경.md 추가, .mcp.json은 PYTHONPATH로 새 위치 사용
- pier-program 내부 .git 제거(이력은 backup\pier-program_20261028.bundle에 보관)
- worktree 가이드 반영(CLAUDE.local.md, HANDOFF.md)
- D:\Rhino-MCP 경로 참조를 새 위치로 치환

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-10-28 11:20:18 +09:00

723 lines
36 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Prism Design System — Medium Profile: Screen (Layer 2c)
| 버전 | 수정일 | 수정자 | 주요 변경 내용 | 비고 |
| :--- | :--- | :--- | :--- | :--- |
| v0.1.0 | 2026-07-28 | 조선두 | 신규 작성 — 단위·브레이크포인트·타입 바인딩·런타임 테마·인터랙션 상태·포커스·터치·Elevation·모션·접근성·검증 | 최초 버전 |
| v0.1.1 | 2026-07-29 | HMCM | §4.3 md 6col 612→**624px**·xl 8col 776→**920px** 정정, RT-2 문언 정정(n+6/n+4 강등), RT-5 신설(강등 병용 금지), mobile 적용 범위에 **md(태블릿)** 추가, 하위 실행 명세(M3·M4) 연결 | 진단 N2·N3·S1·S2·G3 보정 |
| 메타 | 값 |
| :--- | :--- |
| 파일 | `design.medium.screen.md` |
| 계층 | **Layer 2c — Medium Profile: Screen (Web + Mobile)** |
| 역할 | Layer 1의 상대값을 **rem·dp로 실체화** + **인터랙션 계층 전량 정의** |
| 상위 | `design.foundations.md` (Layer 1) |
| 하위 | 스크린 계열 Layer 3 모듈 — 실행 명세: `webapp/module.webapp.md` (M3) · `mobile/module.mobile-tablet.md` (M4) |
| 로드 정책 | **온디맨드** |
> **본 파일은 전량 신설 영역이다.** `print`·`presentation` 프로파일이 다루지 않는 **상태·포커스·터치·모션·런타임 테마**가 여기에 있다. 벤치마킹 조사(`design.benchmark`)는 착수 시점에 "정적 매체" 전제로 이 영역을 **§15 이식 부적합**으로 배제했으나, 매체 범위 확대에 따라 **§19에서 24개 항목이 부활**했다. 본 파일이 그 24개 항목의 목적지다.
---
## 목차
- [1. 프로파일 개요](#1-프로파일-개요)
- [2. 단위](#2-단위)
- [3. 브레이크포인트·그리드](#3-브레이크포인트그리드)
- [4. 타입 바인딩](#4-타입-바인딩)
- [5. 색상·런타임 테마](#5-색상런타임-테마)
- [6. 인터랙션 상태](#6-인터랙션-상태)
- [7. 포커스](#7-포커스)
- [8. 터치·포인터](#8-터치포인터)
- [9. Elevation · Z-index](#9-elevation--z-index)
- [10. 모션](#10-모션)
- [11. 접근성](#11-접근성)
- [12. 개폐형 컴포넌트](#12-개폐형-컴포넌트)
- [13. 검증 체크리스트](#13-검증-체크리스트)
---
# 1. 프로파일 개요
## 1.1 적용 조건
| 적용 | 비적용 |
| :--- | :--- |
| 웹앱 (대시보드·관리도구·폼) | 인쇄 배포용 편람 → `print` |
| 모바일앱 (iOS · Android) | 강연 슬라이드 → `presentation` |
| 웹 문서·가이드 사이트 | |
| 화면에서 스크롤하며 읽는 PDF | |
| 웹 페이지의 인쇄 스타일시트 | *(본 프로파일 + `print` §9 병용)* |
## 1.2 web / mobile 분기
본 프로파일은 **하나의 프로파일에 두 변형(variant)** 을 둔다. 별도 프로파일로 쪼개지 않는 이유는 토큰의 90%가 공통이기 때문이다.
| 항목 | `screen.web` | `screen.mobile` |
| :--- | :--- | :--- |
| 단위 | `rem` (root 16px) | `dp` / `pt` (= 16 기준) |
| 브레이크포인트 | 5단계 (§3.1) | `xs`·`sm`·**`md`(태블릿)** 사용 (v0.1.1) |
| 포인터 | mouse 우선 + touch 대응 | **touch 전용** |
| 히트 영역 | 최소 44px (권장 48) | **최소 48dp** |
| hover 상태 | 사용 | **사용하지 않음** (§6.5) |
| 포커스 링 | **필수** | 키보드·스위치 연결 시 필수 |
| 테마 전환 | 사용자 설정 + OS 연동 | **OS 연동 필수** |
## 1.3 매체 상수
```yaml
medium: screen
variants: [web, mobile]
unit_primary: rem # web
unit_mobile: dp
root_font_size: 16px # = 1rem
type_base: 1rem
space_base_unit: 8px # 0.5rem
baseline_grid: none # Layer 1 v0.1.1 §4.3 — print 한정
contrast_target: AA # ΔG >= 55 필수 / 70 권장
elevation_mode: shadow # 유일하게 그림자를 허용하는 매체
alpha_allowed: true # Layer 1 CS-4 예외
motion: true
interaction: true
runtime_theme: true
```
---
# 2. 단위
## 2.1 rem 우선
| ID | 규칙 | 근거 |
| :--- | :--- | :--- |
| **UN-1** | 타입 크기·간격은 **`rem`** 으로 표기한다. `px` 하드코딩을 금지한다 | 사용자 브라우저 글자 크기 설정을 존중해야 한다 |
| **UN-2** | root font-size를 **`px`로 고정하지 않는다** (`html { font-size: 16px }` 금지). 브라우저 기본값(통상 16px)을 그대로 쓴다 | UN-1과 동일 취지 — 고정하면 사용자 설정이 무효화된다 |
| **UN-3** | **보더 굵기·1px 헤어라인은 `px`** 를 쓴다. 확대 시 굵어질 이유가 없다 | Layer 1 SW-2 |
| **UN-4** | 미디어 쿼리 브레이크포인트는 **`px`** 를 쓴다 (뷰포트는 사용자 글자 설정과 무관) | |
| **UN-5** | 모바일은 플랫폼 단위(`dp` / `pt`)를 쓰되 **16 기준 환산을 동일하게** 유지한다 | |
> **[대조]** `print` 프로파일은 상대 단위를 **금지**하고 절대 단위(mm·pt)만 쓴다. GOV.UK가 인쇄 스타일시트에서 `rem`→`pt`로 전환하는 이유가 이것이다 [출처: design.benchmark, §15 / T1]. **같은 시스템 안에서 매체별로 정반대 규칙이 성립**한다.
## 2.2 환산
```
1rem = 16px (브라우저 기본)
0.5rem = 8px = space.100
1dp(Android) ≈ 1pt(iOS) ≈ 1px@1x
```
---
# 3. 브레이크포인트·그리드
## 3.1 브레이크포인트
Layer 1 §5.2의 2x 파생 원리(12 → 8 → 4)를 적용한다. [출처: design.benchmark, M3]
| ID | 범위 (px) | 대상 | **컬럼** | 마진 | 거터 | 컨테이너 |
| :--- | :--- | :--- | :-: | ---: | ---: | :--- |
| **`xs`** | 0 – 599 | 모바일 세로 | **4** | 16 | 16 | fluid |
| **`sm`** | 600 – 903 | 모바일 가로 · 소형 태블릿 | **8** | 24 | 16 | fluid |
| **`md`** | 904 – 1239 | 태블릿 · 소형 노트북 | **8** | 32 | 24 | fluid |
| **`lg`** | 1240 – 1599 | 데스크톱 | **12** | 32 | 24 | fluid |
| **`xl`** | 1600 – | 대형 디스플레이 | **12** | 24 | 24 | **1440 고정** |
| ID | 규칙 |
| :--- | :--- |
| **BP-1** | 브레이크포인트는 **위 5개만** 사용한다. 컴포넌트별 임의 브레이크포인트를 두지 않는다 |
| **BP-2** | `xl`에서 컨테이너를 **1440px로 고정**한다. 무한 확장은 measure를 파괴한다 (§4.3) |
| **BP-3** | 미디어 쿼리는 **min-width 기준**(mobile-first)으로 작성한다 |
| **BP-4** | 컬럼 수는 Layer 1 GC-1의 허용 집합(12·8·6·4·3) 안에 있다 ✓ |
## 3.2 그리드 정합
Layer 1 GC-3(잔차 0)을 전 브레이크포인트에서 검증했다.
| BP | 기준 폭 | 마진 | 콘텐츠 | 컬럼수 | 거터 | **컬럼 폭** | 검증 |
| :--- | ---: | ---: | ---: | :-: | ---: | ---: | :--- |
| `xs` | 360 | 16 | 328 | 4 | 16 | **70** | 4×70 + 3×16 = 328 ✓ |
| `sm` | 600 | 24 | 552 | 8 | 16 | **55** | 8×55 + 7×16 = 552 ✓ |
| `md` | 904 | 32 | 840 | 8 | 24 | **84** | 8×84 + 7×24 = 840 ✓ |
| `lg` | 1240 | 32 | 1176 | 12 | 24 | **76** | 12×76 + 11×24 = 1176 ✓ |
| **`xl`** | **1440** | 24 | **1392** | 12 | 24 | **94** | 12×94 + 11×24 = 1392 ✓ |
> `fluid` 브레이크포인트에서 컬럼 폭은 뷰포트에 따라 변하며, 위 값은 **각 구간 최소 폭에서의 값**이다. 정합식은 폭과 무관하게 성립한다.
## 3.3 책임 분할
| ID | 규칙 | 근거 |
| :--- | :--- | :--- |
| **GD-1** | **최상위 레이아웃 영역만 그리드에 정렬**한다. 내부는 스페이싱 토큰으로 배치한다 | Layer 1 GR-1 / T15 |
| **GD-2** | CSS Grid의 `grid-template-columns`는 **레이아웃 컨테이너에만** 선언한다 |
| **GD-3** | 컴포넌트는 자기 폭을 모른 채 동작해야 한다. 컴포넌트 내부에 컬럼 수를 하드코딩하지 않는다 |
## 3.4 스페이싱 실체화
| 토큰 | 배수 | **px** | **rem** | 별칭 |
| :--- | :---: | ---: | ---: | :--- |
| `space.025` | 0.25× | 2 | 0.125 | — |
| `space.050` | 0.5× | 4 | 0.25 | `inline-gap` |
| **`space.100`** | **1×** | **8** | **0.5** | `text-block` |
| `space.150` | 1.5× | 12 | 0.75 | `paragraph` |
| `space.200` | 2× | 16 | 1 | `card-padding`, 거터(xs·sm) |
| `space.300` | 3× | 24 | 1.5 | `block-gap`, 거터(md·lg·xl) |
| `space.400` | 4× | 32 | 2 | `zone-gap`, 마진(md·lg) |
| `space.600` | 6× | 48 | 3 | `section-gap` |
| `space.800` | 8× | 64 | 4 | — |
**반경**: `radius.md`(1×) = **8px**. `sm` 4px / `lg` 12px / `pill` 9999px / `none` 0.
**획**: `hairline` **1px** / `regular` 2px / `bold` 3px / `icon` 2px / `connector` 2px
> 스크린은 픽셀 그리드가 정밀하므로 1px 헤어라인이 유효하다. `print`(0.20mm)·`presentation`(2px)과 달리 가장 가는 획을 쓸 수 있는 매체다.
---
# 4. 타입 바인딩
## 4.1 스케일 실체화
`size(n) = 1rem × 1.125ⁿ`. 반올림 단위 **1px** 후 rem 역산.
| 역할 | `n` | **px** | **rem** | 행간 | 웨이트 |
| :--- | :-: | ---: | ---: | :-: | :--- |
| `caption` | −2 | **13** | 0.8125 | 1.55 | Medium 500 |
| `body-sm` | −1 | **14** | 0.875 | 1.70 | Regular 400 |
| **`body`** | **0** | **16** | **1** | **1.70** | Regular 400 |
| `body-lg` | +1 | **18** | 1.125 | 1.70 | Regular 400 |
| `title-sm` | +2 | **20** | 1.25 | 1.50 | Medium 500 |
| `title` | +4 | **26** | 1.625 | 1.50 | Bold 700 |
| `title-lg` | +6 | **32** | 2 | 1.50 | Bold 700 |
| `display` | +9 | **46** | 2.875 | 1.25 | Regular 400 / Medium 500 |
| `display-lg` | +12 | **66** | 4.125 | 1.25 | Regular 400 |
## 4.2 반응형 타입
| ID | 규칙 |
| :--- | :--- |
| **RT-1** | **`body` 계열은 브레이크포인트와 무관하게 고정**한다. 모바일에서 본문을 줄이지 않는다 |
| **RT-2** | `xs`·`sm`에서 `display-lg`는 **n=+6**, `display`는 **n=+4**로 강등한다 — `title-lg`·`title`과 동일 값. (표·tokens.json이 정본. v0.1.1 문언 정정 — 구판 "−3 이동"은 값과 불일치했다) |
| **RT-3** | 계단식(stepped) 방식을 기본으로 한다. **fluid(`clamp()`)는 `display` 계열에만** 선택적으로 허용한다 |
| **RT-4** | fluid를 쓰더라도 **하한·상한은 스케일 인덱스 값**이어야 한다. 임의 값을 넣지 않는다 |
| **RT-5** | 강등된 `display` 계열과 `title-lg`·`title`을 **같은 화면에 병용하지 않는다** — 동일 크기로 위계가 소멸한다 (TH-1 보전, v0.1.1 신설) |
| 역할 | `lg`·`xl`·`md` | **`xs`·`sm`** (−3) |
| :--- | ---: | ---: |
| `display-lg` | 66px (n=+12) | **32px** (n=+6) |
| `display` | 46px (n=+9) | **26px** (n=+4) |
| `title-lg` | 32px | 32px (유지) |
| `body` | 16px | **16px (불변)** |
> **RT-1의 근거**: 모바일에서 본문을 14px로 줄이는 관행이 흔하나, 이는 화면이 작다는 이유로 **가독성을 희생**하는 것이다. 화면이 작으면 **한 줄의 글자 수가 줄 뿐**, 글자가 작아져야 할 이유는 없다.
## 4.3 Measure
| BP | 본문 최대 컬럼 병합 | 폭 | **measure** (16px) | 판정 |
| :--- | :---: | ---: | ---: | :--- |
| `xs` | 4 col (전폭) | 328px | 20.5 em | ✓ 다단 하한 |
| `sm` | 8 col (전폭) | 552px | 34.5 em | ✓ 1단 범위 |
| `md` | 6 / 8 col | 624 / 840px | 39.0 / 52.5 em | **6col ✗ (38 초과)** / **8col ✗** — MS-1로 제한 |
| `lg` | 8 col | 776px | 48.5 em | **✗** |
| `xl` | **8 col** | 920px | 57.5 em | **✗** |
| ID | 규칙 |
| :--- | :--- |
| **MS-1** | **연속 본문 블록에 `max-width: 38em`을 강제**한다. 컨테이너가 넓어도 본문은 넓어지지 않는다 |
| **MS-2** | `md` 이상에서 본문을 전폭으로 흘리지 않는다. 남는 폭은 **사이드바·목차·여백**으로 쓴다 |
| **MS-3** | 표·코드 블록·도판은 MS-1의 예외다 (가로 스크롤 허용) |
> **MS-1은 본 프로파일에서 가장 자주 위반되는 규칙이다.** `xl` 컨테이너 1440px에 본문을 8컬럼(776px)으로 채우면 48.5em으로 상한(38em)을 27% 초과한다. **컨테이너 폭 = 본문 폭이 아니다.**
## 4.4 자간
| 역할 | 자간 | 근거 |
| :--- | :--- | :--- |
| `display-lg` (66px) | **−0.02em** | Layer 1 TR-1 (60px 이상) ✓ |
| `display` (46px) 이하 | **0** | TR-1 미충족 |
| `xs`·`sm`의 `display-lg` (32px) | **0** | 축소되어 TR-1 미충족 |
| `.label` 톤 | +0.12em | TR-4 |
| `.eyebrow` 톤 | +0.18em | TR-4 |
---
# 5. 색상·런타임 테마
## 5.1 테마 구조
**같은 토큰 이름, 다른 값셋.** 라이트/다크는 별개 팔레트가 아니라 **동일 `sys` 토큰의 두 바인딩**이다. [출처: design.benchmark, M1 · T13]
```jsonc
"sys.pair.surface-default": {
"light": { "container": "ref.sand", "on": "ref.brown", "deltaG": 68, "measured": 9.02 },
"dark": { "container": "ref.obsidian", "on": "ref.sand", "deltaG": 91, "measured": 17.02 }
}
```
## 5.2 대칭 미러링
Layer 1 §6.3의 미러링을 **출발점**으로 삼되, 전 페어를 재검증한다. [출처: design.benchmark, M17]
### 브랜드 SAMAN 기준 다크 테마 페어 (검증 완료)
| 역할 | Light | G | Dark | G | ΔG | 실측 | 판정 |
| :--- | :--- | :-: | :--- | :-: | :-: | ---: | :--- |
| `surface.base` / `on` | sand / brown | 4 / 72 | obsidian / sand | 95 / 4 | 91 | **17.02** | AAA |
| `on-subtle` | brown | 72 | subtle `#D3D3D3` | 15 | 80 | **12.61** | AAA |
| `accent` | orange | 47 | orange | 47 | 48 | **4.71** | AA — 대형 권장 |
| `base`(green) **텍스트** | green (ΔG 65, 8.00) | 69 | **사용 금지** | — | 26 | **2.13** | **✗ FAIL** |
| ID | 규칙 |
| :--- | :--- |
| **TM-S1** | 미러링 후 **전 페어 대비를 프로그램으로 재계산**한다 (Layer 1 TM-1·CG-3) |
| **TM-S2** | 미러링이 성립하지 않는 색은 **테마별 값을 개별 지정**한다 |
| **TM-S3** | **다크 테마에서 `base` 계열을 텍스트로 쓰지 않는다.** 면·도형으로만 (실측 2.13:1) |
| **TM-S4** | 정적 식별색(Layer 1 §2.6)은 미러링 대상이 아니다 |
| **TM-S5** | 상태색(Layer 1 §2.7)은 다크 테마용 `container`를 **별도 등록**한다. 라이트용 밝은 container를 다크에 그대로 쓰지 않는다 |
## 5.3 테마 전환
| ID | 규칙 | 근거 |
| :--- | :--- | :--- |
| **TC-1** | **OS 설정(`prefers-color-scheme`)을 기본으로 따른다** | Spectrum device color mode 연동 [§19] |
| **TC-2** | 사용자가 **명시적으로 재정의**할 수 있어야 한다 (light / dark / system 3택) |
| **TC-3** | 재정의 값은 **영속 저장**한다 |
| **TC-4** | 테마는 **`data-theme` 속성 + CSS 변수**로 구현한다. JS로 색을 직접 조작하지 않는다 | ADS `data-color-mode` 방식 [§19] |
| **TC-5** | 전환 시 **색상만 애니메이션**한다. 레이아웃은 변하지 않아야 한다 |
| **TC-6** | 테마 전환 애니메이션은 `prefers-reduced-motion`에서 즉시 전환으로 처리한다 |
```css
:root { /* light 값셋 */ }
:root[data-theme="dark"] { /* dark 값셋 */ }
@media (prefers-color-scheme: dark) {
:root:not([data-theme="light"]) { /* dark 값셋 */ }
}
```
## 5.4 알파 색 (Layer 1 CS-3 예외)
Layer 1 CS-3은 알파 색을 금지하나, **CS-4에 따라 `screen` 매체는 예외**다. [출처: design.benchmark, §19 재판정]
| ID | 규칙 |
| :--- | :--- |
| **AL-1** | 알파 색은 **오버레이·스크림·상태 레이어에만** 허용한다 |
| **AL-2** | **텍스트 색에 알파를 쓰지 않는다.** 대비가 예측 불가능해진다 [출처: Spectrum "알파 색은 대비가 일관되거나 예측 가능하지 않다"] |
| **AL-3** | 알파를 쓴 요소 위의 텍스트는 **합성 결과색으로 대비를 계산**해 등록한다 |
| **AL-4** | **인쇄 스타일시트에서는 알파를 전부 평탄화**한다 (`print` 프로파일 CK-5) |
## 5.5 대비 목표
| 항목 | 목표 | 근거 |
| :--- | :--- | :--- |
| 본문 텍스트 | **ΔG ≥ 55 (AA 4.5:1)** 필수 / **70 권장** | Layer 1 §2.1 매체별 목표 |
| 대형 텍스트 (24px+ 또는 19px+ Bold) | ΔG ≥ 40 (3:1) | WCAG SC 1.4.3 |
| **비텍스트 요소** (보더·아이콘·포커스 링·차트) | **ΔG ≥ 40 (3:1)** | WCAG 2.2 SC 1.4.11 Non-text Contrast |
| 비활성(disabled) 요소 | 대비 요건 면제 | WCAG 예외 |
> `print`·`presentation`이 AAA를 목표로 하는 것과 달리 `screen`은 **AA 필수 / AAA 권장**이다. 사용자가 밝기·확대·테마를 제어할 수 있고, 업계 표준 준수가 상호운용성에 직결되기 때문이다. [출처: design.benchmark, §16 상충-6]
---
# 6. 인터랙션 상태
> 이 장은 `print`·`presentation`에 **존재하지 않는다.** 상태는 스크린 고유 개념이다.
## 6.1 상태 목록
| 상태 | 정의 | 트리거 |
| :--- | :--- | :--- |
| `default` | 기준 | — |
| `hover` | 포인터가 위에 있음 | mouse (**touch 없음**) |
| `focus` | 키보드 포커스 | Tab · 스위치 · 스크린리더 |
| `active` | 눌리는 중 | mousedown · touchstart |
| `selected` | 선택됨 (지속) | 사용자 선택 |
| `disabled` | 사용 불가 | 조건 미충족 |
| `loading` | 처리 중 | 비동기 작업 |
## 6.2 GRADE 시프트 방식 (본 시스템 기본)
상태 표현을 **불투명도 오버레이가 아니라 GRADE 스텝 이동**으로 정의한다. Layer 1 §2.8의 강조 단계 정량화를 상태에 적용한 것이다. [출처: design.benchmark, M18 (Carbon "Selected = 1 full step, Active = 2 full steps")]
| 상태 | **GRADE 시프트** | 비고 |
| :--- | :---: | :--- |
| `default` | 0 | — |
| `hover` | **ΔG ± 4** (½ 스텝) | 라이트는 어둡게(+), 다크는 밝게(−) |
| `focus` | **0** | 색을 바꾸지 않는다. **포커스 링만** (§7) |
| `active` | **ΔG ± 8** (1 스텝) | |
| `selected` | **ΔG ± 12** (1½ 스텝) 또는 면 반전 | |
| `disabled` | `on-subtle` 사용 | **불투명도를 쓰지 않는다** |
| `loading` | `default` 유지 + 진행 표시 | 색을 바꾸지 않는다 |
**GRADE 시프트를 기본으로 삼는 이유**
1. **테마 무관** — 라이트/다크에서 부호만 뒤집으면 되고, 별도 값셋이 불필요하다
2. **대비 예측 가능** — 시프트 후 대비를 GRADE 뺄셈으로 즉시 검증할 수 있다
3. **알파 합성 없음** — 배경이 무엇이든 결과가 결정적이다
4. **인쇄 스타일시트 호환** — 상태가 없는 인쇄에서 `default`만 남기면 그만이다
## 6.3 State Layer 방식 (대안)
GRADE 시프트가 불가능한 경우(이미지 위 버튼, 그라디언트 표면 등)에만 사용한다.
| 상태 | 불투명도 |
| :--- | ---: |
| `hover` | **8%** |
| `focus` | **10%** |
| `active` (pressed) | **10%** |
| `dragged` | **16%** |
> **[CONFLICT DETECTED / 미검증]** MD3 공식 문서와 위젯은 focus·pressed를 **10%**로 기재하나, `material-web` v0.192 토큰은 **12%** 다 [출처: design.benchmark, §18-2]. 본 시스템은 **문서 기재값(10%)** 을 채택하되, 이 불일치를 인지하고 있음을 명시한다. 값 자체보다 **드리프트가 존재한다는 사실**이 중요하다 (Layer 1 F9).
| ID | 규칙 |
| :--- | :--- |
| **SL-1** | State Layer는 **§6.2가 불가능할 때만** 쓴다 |
| **SL-2** | State Layer 위 텍스트의 대비는 **합성 결과색으로 계산**해 등록한다 (AL-3) |
| **SL-3** | State Layer를 **두 개 이상 겹치지 않는다** (hover + active 동시 적용 금지 — 더 강한 쪽만) |
## 6.4 disabled — 불투명도 금지
| ID | 규칙 | 근거 |
| :--- | :--- | :--- |
| **DS-1** | `disabled`를 **불투명도로 표현하지 않는다.** `on-subtle` 토큰(ΔG 40~54)을 쓴다 | Layer 1 §2.8 / M19 — MD3 disabled 38%의 *발상*만 차용하고 구현은 대체 |
| **DS-2** | `disabled` 요소도 **최소한 형태는 판별 가능**해야 한다 (완전한 소실 금지) |
| **DS-3** | `disabled` 이유를 **인접 텍스트로 설명**한다. 색만으로 "쓸 수 없음"을 전달하지 않는다 (Layer 1 CO-1) |
| **DS-4** | 가능하면 `disabled` 대신 **활성 상태 + 오류 메시지**를 쓴다. 비활성 컨트롤은 이유를 알려주지 못한다 |
## 6.5 mobile — hover 없음
| ID | 규칙 |
| :--- | :--- |
| **HV-1** | `screen.mobile`에서 **`hover` 상태를 쓰지 않는다** |
| **HV-2** | `hover`에서만 드러나는 정보·액션을 두지 않는다. **터치 기기에서 접근 불가**가 된다 |
| **HV-3** | 웹에서도 `hover`는 **보강**이어야 한다. `hover` 없이도 기능이 완전해야 한다 |
| **HV-4** | `@media (hover: hover)` 로 hover 스타일을 격리한다 |
---
# 7. 포커스
> 포커스 링은 **장식이 아니라 접근성 필수 요소**다. `outline: none`은 본 시스템에서 결함으로 판정한다.
## 7.1 포커스 링 스펙
| 항목 | 값 | 근거 |
| :--- | :--- | :--- |
| **굵기** | **3px** | GOV.UK `$govuk-focus-width: 3px` [§19] |
| **오프셋** | **2px** | 요소 경계와 분리 |
| **형태** | **이중 링** — 외곽 고대비색 + 내곽 대비색 | 어떤 배경에서도 최소 한 겹이 보인다 |
| **대비** | 인접색 대비 **ΔG ≥ 40 (3:1)** | WCAG 2.2 SC 1.4.11 |
| **반경** | 요소 반경 + 오프셋 | 시각 정합 |
## 7.2 규칙
| ID | 규칙 |
| :--- | :--- |
| **FC-1** | **`outline: none`을 대체 없이 선언하지 않는다** |
| **FC-2** | `:focus-visible`을 사용해 **마우스 클릭 시에는 링을 표시하지 않는다** (키보드·스위치에만) |
| **FC-3** | 포커스 링은 **어떤 요소에도 잘리지 않는다** (`overflow: hidden` 컨테이너 주의) |
| **FC-4** | 포커스 링은 **색만으로 구분되지 않는다** — 굵기·오프셋이 형태적 신호를 만든다 (Layer 1 CO-1) |
| **FC-5** | 포커스는 **`focus` 상태에서 배경색을 바꾸지 않는다** (§6.2). 링만으로 표시한다 |
| **FC-6** | 커스텀 컨트롤은 **네이티브와 동일한 포커스 동작**을 제공해야 한다 |
## 7.3 포커스 순서
| ID | 규칙 |
| :--- | :--- |
| **FO-1** | 포커스 순서는 **시각적 순서와 일치**해야 한다 |
| **FO-2** | **양수 `tabindex`를 사용하지 않는다** (`0` 또는 `-1`만) |
| **FO-3** | 모달·오버레이는 **포커스를 가둔다**(focus trap). 닫으면 **호출 요소로 복귀**한다 |
| **FO-4** | 반복 내비게이션 앞에 **건너뛰기 링크(skip link)** 를 둔다 |
---
# 8. 터치·포인터
## 8.1 히트 영역
| 변형 | **최소** | **권장** | 근거 |
| :--- | :---: | :---: | :--- |
| `screen.web` | **44 × 44px** | 48 × 48px | WCAG 2.5.5 Target Size (Enhanced, AAA) |
| `screen.mobile` | **48 × 48dp** | 48 × 48dp | Spectrum "Touch hit area 48px" [§19] |
> WCAG 2.2 SC 2.5.8 Target Size (Minimum, **AA**)는 24×24 CSS px이나, 본 시스템은 **44px를 하한**으로 채택한다. 24px는 최소 적합 기준이지 권장값이 아니다.
| ID | 규칙 |
| :--- | :--- |
| **HT-1** | **시각 크기와 히트 영역을 분리**한다. 아이콘이 20px여도 히트 영역은 44px여야 한다 |
| **HT-2** | 히트 영역 확장은 **패딩 또는 의사요소(`::before`)** 로 구현한다 |
| **HT-3** | 인접 히트 영역 사이에 **최소 `space.100`(8px) 간격**을 둔다 |
| **HT-4** | **인라인 텍스트 링크는 HT-1의 예외**다 (WCAG 예외 조항) |
| **HT-5** | 화면 가장자리 **4px는 히트 영역으로 신뢰하지 않는다** (모바일 시스템 제스처와 충돌) |
## 8.2 포인터 대응
| ID | 규칙 |
| :--- | :--- |
| **PT-1** | `@media (pointer: coarse)` 에서 히트 영역을 **48px로 상향**한다 |
| **PT-2** | 드래그로만 가능한 조작을 두지 않는다. **대체 수단을 제공**한다 (WCAG 2.5.7 Dragging Movements) |
| **PT-3** | 커서 모양(`cursor`)에만 의존해 상호작용 가능 여부를 알리지 않는다 |
---
# 9. Elevation · Z-index
> **본 시스템에서 그림자를 허용하는 유일한 매체다.** `print`·`presentation`은 Layer 1 EL-2에 따라 그림자 산출이 금지된다.
## 9.1 Elevation 레벨
Layer 1 EL-4("그림자로 표현할 수 있는 것을 보더로도 표현할 수 있는지 먼저 검토")를 준수한다.
| 레벨 | 용도 | **1순위 표현** | 그림자 (보조) |
| :--- | :--- | :--- | :--- |
| `sunken` | 함몰 영역·입력 필드 | 배경 톤 하향 (ΔG +5~9) | inset 없음 |
| `default` | 기준 | 무처리 | 없음 |
| `raised` | 카드·패널 | **헤어라인 보더** | `0 1px 2px` (약) |
| `overlay` | 드롭다운·팝오버 | 보더 + 여백 격리 | `0 4px 12px` |
| `modal` | 모달·다이얼로그 | 보더 + **스크림** | `0 8px 24px` |
| ID | 규칙 |
| :--- | :--- |
| **EV-1** | **보더를 먼저 쓴다.** 그림자는 보더로 부족할 때만 추가한다 [출처: M5 — ADS "A border is the default approach"] |
| **EV-2** | 그림자는 **깊이를 보조**할 뿐 유일한 신호가 되지 않는다 |
| **EV-3** | 다크 테마에서 그림자는 **거의 보이지 않는다.** 다크에서는 **표면 톤 상향**으로 대체한다 |
| **EV-4** | **인쇄 스타일시트에서 그림자를 전부 제거**한다 |
| **EV-5** | 그림자에 **색상(브랜드 색)을 넣지 않는다.** 중립 검정 알파만 |
## 9.2 스크림 (Scrim)
| 항목 | 값 |
| :--- | :--- |
| 불투명도 | **32%** (MD3) |
| 색 | 중립 검정 |
| 적용 | 모달·바텀시트·드로어 배경 |
| ID | 규칙 |
| :--- | :--- |
| **SC-1** | 스크림 클릭 시 **닫기 동작**을 제공한다 (단, 파괴적 작업 확인 다이얼로그는 예외) |
| **SC-2** | 스크림 위 콘텐츠는 **포커스 트랩** 안에 있어야 한다 (FO-3) |
## 9.3 Z-index 스케일
임의 z-index를 금지하고 **토큰만** 사용한다.
| 토큰 | 값 | 용도 |
| :--- | ---: | :--- |
| `z.base` | 0 | 기본 흐름 |
| `z.raised` | 100 | 카드·상승 요소 |
| `z.sticky` | 200 | 고정 헤더·툴바 |
| `z.dropdown` | 300 | 드롭다운·셀렉트 |
| `z.scrim` | 400 | 스크림 |
| `z.modal` | 500 | 모달·다이얼로그 |
| `z.popover` | 600 | 팝오버 |
| `z.toast` | 700 | 토스트·스낵바 |
| `z.tooltip` | 800 | 툴팁 |
| ID | 규칙 |
| :--- | :--- |
| **ZI-1** | **토큰 외 z-index 값을 쓰지 않는다** (`z-index: 9999` 금지) |
| **ZI-2** | 같은 레벨 내 순서는 **DOM 순서**로 해결한다 |
| **ZI-3** | 새 레벨이 필요하면 **본 파일에 등재 후** 사용한다 |
---
# 10. 모션
## 10.1 지속 시간 토큰
| 토큰 | 값 | 용도 |
| :--- | ---: | :--- |
| `motion.instant` | **0ms** | 즉시 반응 (상태 색 변화) |
| `motion.fast` | **100ms** | 소형 요소 (버튼·체크박스) |
| `motion.moderate` | **200ms** | 중형 (드롭다운·툴팁) |
| `motion.slow` | **300ms** | 대형 (모달·드로어·페이지 전환) |
| `motion.deliberate` | **500ms** | 주의를 끌어야 하는 전환 |
## 10.2 이징 토큰
| 토큰 | 곡선 | 용도 |
| :--- | :--- | :--- |
| `ease.standard` | `cubic-bezier(0.2, 0, 0, 1)` | 기본 — 화면 내 이동 |
| `ease.enter` | `cubic-bezier(0, 0, 0, 1)` | 진입 (감속) |
| `ease.exit` | `cubic-bezier(0.3, 0, 1, 1)` | 퇴장 (가속) |
| `ease.linear` | `linear` | 진행률·로딩 |
## 10.3 규칙
| ID | 규칙 |
| :--- | :--- |
| **MT-1** | **모션은 상태 변화를 설명할 때만** 쓴다. 장식용 애니메이션을 두지 않는다 |
| **MT-2** | **바운스·회전·무한 루프 장식**을 사용하지 않는다 |
| **MT-3** | 애니메이션은 **`transform`·`opacity`만** 변경한다 (레이아웃 리플로 방지) |
| **MT-4** | 진입·퇴장은 **비대칭**으로 한다 (진입 `moderate`, 퇴장 `fast`) |
| **MT-5** | 모션 **없이도 기능이 완전**해야 한다 |
## 10.4 prefers-reduced-motion
```css
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
```
| ID | 규칙 |
| :--- | :--- |
| **RM-1** | `prefers-reduced-motion: reduce`를 **반드시 구현**한다 |
| **RM-2** | 축소 모드에서 애니메이션은 **최종 상태로 즉시 해소**된다. 기능이 사라지지 않는다 |
| **RM-3** | **불투명도 페이드는 축소 모드에서도 허용**한다 (전정 자극이 없다) |
| **RM-4** | 시차(parallax)·자동 재생 캐러셀·화면 밖에서 날아드는 요소는 **축소 모드에서 완전 비활성**한다 |
| **RM-5** | 5초 이상 자동 재생되는 모션에는 **정지 컨트롤**을 제공한다 (WCAG 2.2.2) |
---
# 11. 접근성
> **적합성 목표: WCAG 2.2 Level AA**
## 11.1 필수 요건
| # | 항목 | 기준 |
| :-: | :--- | :--- |
| A1 | 텍스트 대비 | ΔG ≥ 55 (4.5:1) / 대형 ΔG ≥ 40 (3:1) |
| A2 | 비텍스트 대비 | ΔG ≥ 40 (3:1) — 보더·아이콘·포커스 링·차트 |
| A3 | 색 단독 의존 금지 | Layer 1 CO-1 |
| A4 | 포커스 가시성 | §7 |
| A5 | 키보드 조작 완전성 | 마우스 전용 기능 0건 |
| A6 | 히트 영역 | §8.1 |
| A7 | 모션 축소 | §10.4 |
| A8 | 200% 확대 시 콘텐츠 손실 없음 | UN-1·UN-2 |
| A9 | 대체 텍스트 | 의미 있는 이미지 전부 |
| A10 | 폼 레이블 | 시각적 레이블 + 프로그래매틱 연결 |
| A11 | 오류 식별·설명 | 색 + 기호 + 텍스트 |
| A12 | 페이지 제목·랜드마크 | 문서 구조 |
## 11.2 시맨틱
| ID | 규칙 |
| :--- | :--- |
| **SM-1** | **네이티브 요소를 먼저 쓴다.** `<button>` 대신 `<div onclick>`을 쓰지 않는다 |
| **SM-2** | `aria-*`는 **네이티브로 불가능할 때만** 쓴다. 잘못 쓴 ARIA는 없는 것보다 나쁘다 |
| **SM-3** | 헤딩 레벨을 **건너뛰지 않는다** (h2 → h4 금지) |
| **SM-4** | 헤딩 레벨을 **크기 조절 목적으로 쓰지 않는다.** 크기는 타입 토큰이 정한다 |
| **SM-5** | 아이콘 전용 버튼에 **접근 가능한 이름**을 부여한다 |
## 11.3 문안 접근성
브랜드 §5.7의 **방향어 금지**는 스크린에서 특히 중요하다. 화면 낭독 시 "아래 표"는 아무 의미가 없고, 반응형 레이아웃에서 "오른쪽"은 모바일에서 "아래"가 된다. [출처: design.benchmark, T52]
| ID | 규칙 |
| :--- | :--- |
| **AC-1** | 위치 지시어 대신 **이름·번호**로 참조한다 |
| **AC-2** | 링크 텍스트는 **문맥 없이도 목적지를 알 수 있어야** 한다 ("여기를 클릭" 금지) |
| **AC-3** | 오류 메시지는 **무엇이·왜·어떻게** 3요소를 갖춘다 [출처: T-Polaris error 3분할 공식] |
## 11.4 검사 도구와 한계
| ID | 규칙 | 근거 |
| :--- | :--- | :--- |
| **AT-1** | **자동 접근성 검사를 CI 게이트로 강제**한다 (`axe` / `pa11y` 등). 통과하지 못하면 머지하지 않는다 | [출처: M22 — USWDS·GOV.UK "if it doesn't pass, we don't merge"] |
| **AT-2** | **자동 검사는 전체 문제의 약 30%만 발견**한다. 수동 검수를 병행한다 | GDS 인용 [M22] |
| **AT-3** | 수동 검수에는 **키보드 단독 조작·스크린리더·200% 확대** 3종을 포함한다 |
| **AT-4** | **"접근 가능한 컴포넌트로 만들었다"가 접근 가능한 서비스를 보증하지 않는다** | [출처: T49 — USWDS "We test our work. You need to test yours."] |
---
# 12. 개폐형 컴포넌트
`print`에서 물리적으로 불가능했던 개폐형 구조가 `screen`에서는 사용 가능하다. 다만 **GOV.UK가 사용을 권고하지 않는다**는 점을 인지하고 조건을 붙인다. [출처: design.benchmark, T37 · §19]
## 12.1 사용 조건
| ID | 규칙 |
| :--- | :--- |
| **AD-1** | **1순위는 "제목으로 구분된 단일 흐름 + 상단 앵커 목차"** 다. 아코디언은 그것으로 부족할 때만 쓴다 |
| **AD-2** | 아코디언 안에 **처음 방문자가 반드시 봐야 할 정보**를 넣지 않는다 |
| **AD-3** | **모두 펼치기/접기** 컨트롤을 제공한다 |
| **AD-4** | 브라우저 페이지 내 검색(Ctrl+F)으로 **접힌 내용도 찾을 수 있게** 한다 |
| **AD-5** | **인쇄 시 전부 펼친 상태**로 출력한다 |
| **AD-6** | 탭은 **상호 배타적 대안**을 보여줄 때만 쓴다. 순차적 내용에 쓰지 않는다 |
## 12.2 상태 유지
| ID | 규칙 |
| :--- | :--- |
| **AS-1** | 개폐 상태를 **URL 또는 영속 저장**에 반영해 새로고침 후에도 유지한다 |
| **AS-2** | 접힌 섹션에 대한 **딥링크**가 동작해야 한다 (링크 진입 시 자동 펼침) |
---
# 13. 검증 체크리스트
## 13.1 자동 검증 — 빌드 게이트
Layer 1 §8.1의 F1~F9에 더한 프로파일 전용 항목이다. **Stylelint / ESLint / axe를 CI에 연결**한다 [§19 재분류 — 본 매체에서는 자동 강제가 가능하다].
| # | 항목 | 판정 기준 | 실패 시 |
| :-: | :--- | :--- | :--- |
| **W1** | **색 리터럴 금지** | CSS 내 HEX·rgb() 리터럴 0건 (토큰 정의부 제외) | 중단 |
| **W2** | **치수 리터럴 금지** | 스페이싱·반경에 토큰 외 값 0건 | 중단 |
| **W3** | **root font-size 고정 금지** | `html { font-size: <px> }` 0건 (UN-2) | 중단 |
| **W4** | **`outline: none` 검출** | 대체 없는 선언 0건 (FC-1) | 중단 |
| **W5** | **z-index 리터럴** | 토큰 외 z-index 0건 (ZI-1) | 중단 |
| **W6** | **양수 tabindex** | 0건 (FO-2) | 중단 |
| **W7** | **그리드 정합** | 전 브레이크포인트 잔차 0 | 중단 |
| **W8** | **대비 (자동)** | `axe` 색 대비 위반 0건 | 중단 |
| **W9** | **접근 가능한 이름** | 아이콘 버튼·링크 이름 누락 0건 | 중단 |
| **W10** | **대체 텍스트** | `<img>` alt 누락 0건 | 중단 |
| **W11** | **헤딩 레벨 건너뜀** | 0건 (SM-3) | 경고 |
| **W12** | **`prefers-reduced-motion`** | 구현 존재 (RM-1) | 중단 |
| **W13** | **히트 영역** | 인터랙티브 요소 ≥ 44px (인라인 링크 제외) | 경고 |
| **W14** | **본문 max-width** | 연속 본문 블록 ≤ 38em (MS-1) | 경고 |
| **W15** | **테마 페어 대비** | light·dark 전 페어 재계산 통과 (TM-S1) | 중단 |
| **W16** | **알파 텍스트 색** | 0건 (AL-2) | 중단 |
| **W17** | **hover 전용 기능** | `@media (hover: hover)` 밖의 hover 의존 0건 (HV-4) | 경고 |
| **W18** | **모션 속성** | `transform`·`opacity` 외 애니메이션 0건 (MT-3) | 경고 |
## 13.2 수동 검수
| # | 항목 | 방법 |
| :-: | :--- | :--- |
| **M1** | **키보드 단독 조작** | 마우스 없이 전 기능 도달·실행 |
| **M2** | **스크린리더** | 주요 흐름 1회 낭독 확인 |
| **M3** | **200% 확대** | 콘텐츠 손실·가로 스크롤 발생 여부 |
| **M4** | **그레이스케일 판독** | 색 단독 의존 검출 (Layer 1 CO-1) |
| **M5** | **다크 테마 전수** | 전 화면 다크 렌더 확인 (특히 `base` 텍스트 사용 여부 — TM-S3) |
| **M6** | **모션 축소 모드** | 기능 완전성 확인 (RM-2) |
| **M7** | **터치 기기 실기** | hover 의존·히트 영역 실제 확인 |
| **M8** | **인쇄 스타일시트** | 그림자 제거·알파 평탄화·아코디언 전개 확인 (EV-4·AL-4·AD-5) |
| **M9** | **포커스 순서** | Tab 순회가 시각 순서와 일치 (FO-1) |
| **M10** | **저사양·저대역** | 로딩 상태·폴백 확인 |
> **AT-2 재확인**: 자동 검사(§13.1)는 문제의 약 30%만 잡는다. §13.2를 생략하면 나머지 70%가 그대로 배포된다.
---
## 근거 출처 요약
본 파일이 인용한 벤치마킹 항목:
**T13 · T15 · T37 · T49 · T52 · M1 · M3 · M5 · M17 · M18 · M19 · M22 · §16 상충-6 · §18-2 · §19 (재판정 24항 전량)**
전문: `RAG/design.benchmark_2026-07-28_v0.1.0.md`
**§19 부활 항목의 수용 현황**
| 부활 항목군 | 수용 위치 |
| :--- | :--- |
| State layers · 인터랙션 상태 토큰 · 하프스텝 hover · opacity.disabled | §6 (GRADE 시프트로 재설계, State Layer는 대안) |
| Elevation L4·L5 · Scrim · Drop shadow · Z-index · Shadow/Depth | §9 |
| 포커스 링 체계 | §7 |
| 런타임 테마 · `data-color-mode` · device color mode · Contextual tokens | §5.3 |
| 반응형 브레이크포인트 · Fluid type · 아코디언·탭 | §3.1 · §4.2 · §12 |
| `rem`/`em` 상대 단위 | §2.1 |
| Touch hit area | §8.1 |
| Motion 토큰 · 스크린리더 규정 | §10 · §11 |
| Alpha 색상 · Transparent white/black | §5.4 (Layer 1 CS-4 예외) |
| Stylelint 자동 강제 | §13.1 |
**[CONFLICT DETECTED / 미검증]**
- §6.3 MD3 state layer focus·pressed — 문서 **10%** vs `material-web` v0.192 토큰 **12%** [§18-2]. 문서값 채택, 드리프트 존재 명시
**본 시스템 고유 설계**
- **§6.2 GRADE 시프트 방식** — 상태 표현을 불투명도 오버레이가 아니라 GRADE 스텝 이동으로 정의. 테마 무관·대비 예측 가능·인쇄 호환. Carbon의 "1 full step / 2 full steps" 발상(M18)을 GRADE 척도(Layer 1 §2.1) 위에 재구현한 것으로, 조사 대상 8개 시스템 중 동일 구현은 없다 `[자체 정의]`