# 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** | **네이티브 요소를 먼저 쓴다.** `