- route table: extension route for products with an existing visual language, feedback row, derived state tokens stay local - completion: blocking 0 plus reported unverified; hard-gate surface is the touched scope; delivery report starts with what and how to open - design.md created at step 0 with a skeleton, fingerprint line and change log; product-surface rubric reading; direction delegation states the chosen card and reason - layout: hero families, nav forms, footer forms; art-direction page convergence 6 axes; genre sections; image treatments; placeholder conventions; multi-screen order; Tailwind theme mapping - platform: scroll-driven examples, anchor positioning, canvas OT, three/R3F pins, shadcn style prefixes, Safari 26 notes, subgrid - design-gate: four result states, exit codes, cwd module resolution, --url/--out-dir/--strict, oklch contrast, dead selector word match - Korean font budget split from the Latin baseline
54 KiB
layout — 레이아웃과 타이포그래피
4-1 단계에서 읽는다. 이 단계가 끝나면 아무 이펙트 없이도 완성된 페이지여야 한다. 그것이 이후 모든 폴백의 기반이다.
접근성 규범(포커스·키보드·라이브 리전·히트 영역)은 accessibility.md, 입력 촉감·제스처·스프링은 interaction-feel.md, 아이콘 정렬·상태·RTL 뒤집기는 icons.md, 인쇄·이메일 적응은 print-email.md에서 각각 다룬다. 이 문서는 그리드·시선 흐름·타이포그래피 적용·반응형 구조를 다룬다.
이 문서를 읽는 법
| 상황 | 읽을 곳 |
|---|---|
| 그리드·정렬선·읽기 폭을 정한다 | §1 |
| 시선 흐름·그루핑·주요 액션·좋은 상태 신호 | §2 |
| 섹션마다 어떤 토폴로지를 쓸지 정한다 | §2 "의미가 다르면 섹션 토폴로지도 달라야 한다" |
| 히어로 구도를 고른다(분할은 여덟 가운데 하나) · 표제 위 라벨·표제 꼴·리드·행동 | §2-b |
| 내비 형태를 고른다(데스크톱 포함, 고정·흐림 여부) | §2-c → 좁은 화면 구현은 §4-b |
| 푸터에 무엇을 담고 어떤 형태로 둘지 | §2-d |
| 타이포를 적용한다(한글이면 정본은 typography.md §6-5) | §3 |
| 반응형·입력 방식·컨테이너 쿼리·카드 안쪽 정렬선(subgrid) | §4 |
| 화면·페이지가 둘 이상이다(만드는 순서·공통 셸·감사 범위) | §4-m |
| 모바일 내비 구현(쌓기·접기·검사) | §4-b |
| 구조를 바꿨다 | §4-c |
| 1920·2560 초광폭 | §4-d. 분할 히어로를 골랐으면 §4-e도 |
| 콘솔·전문가 도구 | §4-f, §4-h, §4-i |
| 표 | 데이터 테이블 정본은 component-systems.md §11. 카드 변환 구현은 §4-g |
| RTL·다국어·로케일 포맷 | §4-j, §4-k, §4-l |
| 이 단계를 마친다 | §5 |
1. 그리드를 먼저 정한다
공통 정렬선과 역할별 폭
.page {
display: grid;
grid-template-columns:
[full-start] minmax(var(--space-6), 1fr)
[wide-start] minmax(0, 12rem)
[main-start] minmax(0, var(--measure)) [main-end] /* 3단계에서 정한 값이다 */
minmax(0, 12rem) [wide-end]
minmax(var(--space-6), 1fr) [full-end];
}
.page > * { grid-column: main; }
.page > .wide { grid-column: wide; }
.page > .full { grid-column: full; }
main의 읽기 폭은 3단계에서 언어와 폰트에 맞춰 정한 --measure 토큰으로 둔다. 60rem 같은 값이 그 본문 조건을 넘으면 대표 문단의 줄 길이와 확대 상태에서 다시 검증한다. wide·표·작업대는 별도 역할 토큰과 실제 사용 장면으로 검증한다.
ch는 선택한 글꼴의 메트릭에 의존한다. 한글과 라틴의 실제 줄 길이는 글꼴·언어·내용으로 달라지므로, 예시 폭을 보편 수치로 쓰지 말고 대표 문단을 렌더해 확인한다.
이 공통 레일은 본문 폭 · 넓은 블록 · 전체 폭의 정렬 기준이다. 본문 읽기 폭, 증거·표, 작업대는 역할별 컨테이너 토큰(--measure, --container-wide, --workbench-wide)을 써도 된다. 각 역할의 폭·padding을 토큰으로 이름 붙이고 공통 레일과의 관계를 렌더에서 확인한다.
비대칭을 두려워하지 마라
중앙 정렬과 비대칭은 모두 선택지다. 초점, 균형, 내용 군집, 작은 폭에서의 읽기를 실제 콘텐츠로 비교해 고른다. 비대칭에도 규칙이 있어야 한다 — 아무 데나 놓는 것은 비대칭이 아니라 사고다.
- 텍스트 블록을 그리드의 2/3 지점에서 끊고 나머지를 비워두기
- 이미지를 화면 밖으로 흘려보내기(
full을 넘어margin-inline: calc(var(--space-6) * -1)) - 제목과 본문의 시작선을 일부러 어긋내기 — 단, 같은 어긋남을 페이지 전체에서 반복할 때만
벤토 그리드 주의
벤토(크기가 다른 카드들의 격자)는 정보를 묶거나 우선순위를 드러낼 때 쓸 수 있다. 카드 크기 변화만으로 고유성·스크롤 깊이·이탈을 예측할 수는 없다. 아래 장치는 선택지이며, 구조가 필요로 할 때만 쓴다:
- 카드 경계를 없애고 여백만으로 구획
- 그리드에서 한 칸을 의도적으로 비우기
- 카드 하나만 규칙을 깨고 밖으로 나가기
선택자 특정성은 조용히 상쇄된다
관찰 후보 — 엔지니어링 관행. 상위 컨테이너 클래스(.section)와 하위 컴포넌트 클래스(.cta)는 특정성이 같아(0,1,0) 로드 순서가 이긴다. section.hero(0,1,1)처럼 특정성이 다르면 로드 순서와 무관하게 한쪽이 이긴다. 4-e의 초광폭 분할 히어로 사례(상위 container selector와 wide stage의 padding-inline 중 한쪽만 남아 텍스트 폭이 붕괴한 것)는 이 원칙의 구체형이다. 4단계에서 새 컴포넌트 선택자를 쓸 때는 그 선택자가 덮으려는 상위 선택자의 특정성과, 두 선택자가 같은 속성을 정의하는지를 함께 확인한다. [SKILL-FRONTEND-DESIGN]
2. 시선 흐름
사람은 페이지를 읽지 않고 훑는다. 훑는 경로를 설계하는 것이 레이아웃의 본체다.
- 첫 화면이 대표 과업에 필요한 정체성·가치·다음 행동을 충분히 설명하는지 확인한다. 세 요소는 흔한 점검 틀이지 모든 화면의 최대 항목 수가 아니다
- 위계는 크기·굵기·색·여백·위치의 조합으로 만든다. 어떤 두 변수를 반드시 바꿔야 한다는 공식보다, 회색조와 실제 콘텐츠에서 우선순위가 읽히는지를 본다
- 강조하거나 역할을 전환해야 할 때는 리듬·크기·여백·색의 차이로 시선을 머물게 할 수 있다. 모든 섹션의 리듬을 일부러 깨야 하는 것은 아니다
- 스크롤 뒤에도 새 정보나 과업 진전이 있는지 확인한다. 반복 카드가 이탈을 만든다는 보편 증거는 없으므로, 실제 콘텐츠와 사용자 행동으로 판단한다
좋은 상태의 관찰 가능한 신호. 실패 목록만으로는 무엇이 잘된 것인지 판정할 수 없다. 아래는 시작 신호이지 통과선이 아니다 — 하나라도 빠지면 그 이유를 말할 수 있어야 한다.
| 신호 | 보는 법 |
|---|---|
| 회색조 캡처에서 읽는 순서(1→2→3)가 의도와 같다 | 첫 화면을 회색조로 바꿔 짧게 보고 먼저 읽힌 것을 적는다 |
| 한 화면의 주요 시작선(정렬선)이 셋 이하다 | 브랜드·제목·본문·카드의 왼쪽 x값을 잰다 |
| 섹션 간격의 변주가 정보 전환과 일치한다 | 간격이 커지는 자리가 실제로 주제가 바뀌는 자리인지 본다 |
| 본문 문단의 회색 농도가 고르고, 강·약 리듬이 두 단계 이상 있다 | 흐리게 본 화면에서 덩어리가 고르게 회색인지, 강조가 점처럼 찍히는지 본다 |
| 제목과 본문의 차이가 크기·굵기·색·여백 중 둘 이상에서 난다 | 계산된 스타일을 나란히 적는다 |
그루핑 도구에는 선호 순서가 있다
관찰 후보 — 시작값. 관련 요소를 묶을 때는 이 순서로 검토한다.
- 네거티브 스페이스 — 기본값. 관련 항목은 가깝게, 무관한 항목은 멀게 둔다.
- 배경 도형 — 카드·채워진 컨테이너. 그룹이 반드시 하나의 단위로 읽혀야 할 때(선택 가능한 행, 드래그 가능한 카드 등)만 쓴다.
- 구분선 — 최후 수단. 공간이 너무 비싼 밀집 데이터(표, 긴 설정 목록)에서만 쓴다. 헤어라인 두께, 낮은 대비로 두고, 이미 공간이 구조를 만들었다면 큰 gap과 중복해서 쓰지 않는다.
그룹 내 간격과 그룹 간 간격의 비율은 1:2를 시작값으로 둔다 — 그룹 내 --space-2(8px)면 그룹 간은 --space-4(16px) 이상. 비율이 이보다 좁으면 눈이 그룹 경계를 못 잡고 노이즈로 읽는다. 프로젝트 spacing 스케일에 이미 값이 있으면 그것을 그대로 쓰고, 이 비율로 다시 맞추지 않는다. [SKILL-BETTER-LAYOUT]
결정 단위당 주요 액션은 하나다
관찰 후보. 첫 화면은 목차이지 책 전체가 아니다. 결정 단위(히어로 하나, 폼 하나, 다이얼로그 하나, 반복 모듈 하나)마다 주요 액션을 하나로 정하고(색으로 표시하는 방법은 color.md §7), 보조 액션이 2~3개를 넘으면 메뉴 뒤로 묶는다. 레벨 1에서 모든 것을 보여주는 긴 뷰보다, 더 깊이 링크하는 짧은 뷰를 우선한다. [SKILL-BETTER-LAYOUT]
- 상품 목록의 카드별 "담기", 요금제 열마다 있는 가입 버튼처럼 반복 모듈은 모듈마다 하나가 정상 관례다. 요금제는 추천 열 하나만 채우고 나머지는 외곽선으로 두는 것을 시작값으로 한다.
- 구매/정기구독처럼 결과가 다른 두 경로는 "세그먼트 선택 + 단일 CTA"와 "병렬 두 CTA" 중 브리프의 목표 행동 우선순위로 고른다. 둘 다 정당하다.
의미가 다르면 섹션 토폴로지도 달라야 한다
일관성은 같은 거시 그리드를 복제하는 것이 아니다. 연속한 섹션의 역할이 다른데 모두 왼쪽 큰 제목 + 오른쪽 목록이면 구조 슬롭이다. 색·배경·카드 radius를 바꿔도 읽는 동작은 같다.
2단계에서 섹션 역할과 토폴로지를 나란히 적는다.
역할마다 후보가 셋 이상 있다. 칸 안의 순서에는 뜻이 없다 — 첫 번째 것을 고르지 않는다. 고르는 기준은 오른쪽 열이다.
| 역할 | 토폴로지 후보 | 무엇이 고르나 |
|---|---|---|
| 장소·관계 | 지도 + 같은 내용의 목록 / 오는 길을 사진 순서로 / 영업시간·위치 표 | 처음 오는 사람인가, 아는 사람이 시간만 확인하는가 |
| 비용·사양 | 나란히 놓는 비교표 / 조건을 넣으면 값이 나오는 계산기 / 항목과 합계가 있는 견적서 한 장 / 단계별 견적 | 여러 안을 비교하는가, 자기 조건의 값 하나를 구하는가 |
| 절차·판정 | 타임라인 / 하루 일과표 / 전후 사진 쌍 / 체크리스트 / 갈림이 있는 흐름도 | 순서가 중요한가, 결과가 중요한가, 갈림이 있는가 |
| 이미지·재질 | 풀블리드 한 장 / 필름스트립 / 크기가 다른 사진 묶음 / 확대 컷 + 주석 | 사진이 몇 장이고 그중 주인공이 있는가(images.md §2 처리 표) |
| 입력·결과 | 폼 + 결과 인스펙터 / 입력 한 칸과 바로 나오는 답 / 단계형 입력 | 입력 항목 수, 결과를 보면서 고치는가 |
| 신뢰 | 사람 사진과 이름 / 작업 과정 사진 / 실제 문서 사본 / 숫자 하나를 크게 | 실제로 가진 증거가 무엇인가(없는 후기·수치는 만들지 않는다) |
| 기록·목록 | 날짜순 목록 / 카드 격자 / 표 / 달력 | 훑는가, 찾는가, 열끼리 비교하는가 |
표와 "원장·장부"는 기본값이 아니다. 쇼케이스 19종 계측(2026-10-06)에서 12종이 <table>을 썼고 8종에 "원장·장부·ledger"라는 낱말이 있었다. 이 표의 비용·사양 행에 "원장·명세서·비교표" 한 줄만 있었던 탓이다. 표는 여러 행을 열끼리 비교할 때 쓴다. 섹션 이름에 "원장"·"장부"를 쓰려면 그 업종이 실제로 쓰는 낱말인지 확인한다. 배경색만 번갈아 바꾼 띠는 토폴로지가 아니다. 장르별 섹션 흐름 예는 genre-conventions.md에 있다.
다르게 보이기 위해 억지로 바꾸지는 마라. 같은 데이터 묶음을 반복하는 목록은 같은 구조가 맞다. 규칙은 의미 역할이 다르면 읽는 동작도 달라야 한다다. 연속 3개 섹션의 DOM 골격과 회색조 실루엣이 같다면, 그중 둘의 정보 구조부터 다시 설계한다.
같은 액션 행의 직접 자식은 클릭 박스 중심을 잰다
한 액션 묶음의 같은 행에 놓인 직접 자식인 채운 버튼과 텍스트 링크는 align-items:center만으로 시각 중심이 맞지 않을 수 있다. 중첩된 아이콘·배지까지 한꺼번에 비교하거나, 모바일에서 세로로 쌓인 액션에 이 규칙을 적용하면 오탐이다.
- 두 액션의 실제 hitbox와 정렬을 함께 측정한다. 터치 타깃 기준(24px 하드 게이트와 44px 계약의 두 층)은 accessibility.md §8이 정본이다
getBoundingClientRect()의 중심 y 차이가 브라우저 CSS 픽셀 기준 2px 이하여야 한다- 텍스트 baseline이 아니라 클릭 가능한 박스 전체를 비교한다
- 보조 링크의 hitbox를 키운 결과 밀도가 나빠지면, 행동 중요도·오입력·레이아웃을 보고 세로 배치나 다른 구조를 검토한다
패널을 숨기면 그리드 트랙도 회수한다
데스크톱 목록+인스펙터 분할 작업대에서 패널만 display:none으로 숨기고 2열 grid-template-columns를 유지하면 큰 빈 면이 남는다. 닫힌 상태는 가시성 변화가 아니라 레이아웃 상태 변화다. 모바일에서 인스펙터가 시트·전체 화면으로 열리는 구조에는 데스크톱 2px 트랙 계약을 그대로 적용하지 않고, 시트 닫힘·배경 복귀·포커스 복원을 별도로 잰다.
.workbench { grid-template-columns: minmax(0, 1fr) minmax(20rem, .55fr); }
.workbench.is-inspector-closed { grid-template-columns: minmax(0, 1fr); }
.workbench.is-inspector-closed .inspector { display: none; }
닫힘 상태에서 목록 오른쪽 끝과 작업대 오른쪽 끝의 차이를 2px 이하로 재고, 행 선택 뒤 2열이 복원되는 것도 함께 단언한다.
컨트롤은 정적 텍스트와 다르게 보여야 한다
관찰 후보 — 양방향. 모든 상호작용 요소는 배경·테두리·일관된 배치 구역 중 하나로 정적 텍스트와 구별되어야 한다. 주변 텍스트와 똑같이 스타일된 컨트롤은 컨트롤로 읽히지 않는다. 역방향 함정도 같은 비중으로 본다 — 옆의 진짜 버튼과 똑같이 생긴 클릭 불가 배지·라벨은 헛클릭을 모은다. [SKILL-BETTER-LAYOUT]
광학 정렬
아이콘이 있는 버튼, 재생 삼각형, 비대칭 아이콘처럼 기하학적 중심은 맞아도 눈에는 어긋나 보이는 보정은 icons.md §6을 본다.
점진적 공개에는 눈에 보이는 단서가 필요하다
관찰 후보 — 시작값. 숨긴 콘텐츠에 단서가 없으면 없는 것과 같다. 프로젝트에 기존 disclosure 패턴이 있으면 그것을 쓰고, 없을 때만 아래 레시피를 시작값으로 검토한다.
- 피킹 아이템: 가로 스크롤러·캐러셀에서 다음 카드가 컨테이너 엣지 너머로
16px~32px삐져나오게 한다. - 디스클로저 컨트롤: 접힌 섹션에는 쉐브론이나 "더 보기"류 컨트롤을 두되, 숨겨진 개수를 라벨에 명시한다 — 예: "결과 12개 더 보기". 개수 없는 "더 보기"만으로는 약하다.
- 잘림 단서: 클램프된 텍스트는 말줄임표와 함께 전체 값에 도달할 수단(펼치기·링크·툴팁)을 같이 둔다.
.scroller {
--peek: 24px; /* 다음 카드가 비치는 폭 — 시작값 16~32px, 실측 조정 */
display: flex;
gap: var(--space-2);
overflow-x: auto;
padding-inline: var(--space-6);
scroll-padding-inline: var(--space-6);
scroll-snap-type: x mandatory;
}
.scroller > * {
/* 100% 는 패딩을 뺀 콘텐츠 폭이다. 오른쪽 패딩 영역에도 다음 카드가 비치므로 그만큼 덜 뺀다 */
flex: 0 0 calc(100% - var(--space-2) - max(0px, var(--peek) - var(--space-6)));
scroll-snap-align: start;
}
실제 렌더에서 피크 폭이 16~32px인지 확인한다.
[SKILL-BETTER-LAYOUT]
표의 숫자는 끝에, 텍스트는 시작에 정렬한다
관찰 후보. 정렬 기준선의 작은 집합을 고르고 고수한다. 표 안에서 숫자는 트레일링 엣지에, 텍스트는 리딩 엣지에 정렬한다(데이터 테이블 전체 규칙은 component-systems.md §11) — 숫자 자체의 자릿수 정렬(tabular-nums)은 별개 축이며 typography.md를 따른다. 물리적 left/right 대신 리딩/트레일링으로 사고하는 이유는 4-j "방향 독립 레이아웃" 절을 본다. [SKILL-BETTER-LAYOUT]
구체적인 내비 라벨이 우산 용어보다 낫다
관찰 후보. 내비게이션이나 섹션 라벨을 지을 때 "홈"·"메뉴" 같은 우산 용어보다, 그 화면이 실제로 하는 일을 가리키는 구체적 이름("진행 상황"·"보관함")이 더 잘 읽힌다. 구체적 라벨은 클릭 전에 무엇을 보게 될지 예측하게 한다. [SKILL-APPLE-DESIGN]
모달 액션 행은 스크롤 영역과 분리한다
프로젝트 계약. 리사이즈 가능한 패널 바닥, 고정 높이 모달의 폴드 아래, 확장하는 키보드 뒤처럼 잘릴 수 있는 자리에 확인·취소 같은 핵심 액션을 두지 않는다. 모달 콘텐츠가 스크롤되면 액션 행은 함께 스크롤되지 않고 안정된 자리(스티키 footer 또는 뷰 상단)에 남는다. native <dialog> 포지셔닝·내부 scroll owner 검증은 preflight.md의 모달 체크리스트와 상호 참조한다. [SKILL-BETTER-LAYOUT]
액션이 잘려 도달할 수 없으면 조작 불능으로 하드 게이트다.
2-b. 히어로 구도 패밀리 — 분할은 여덟 가운데 하나다
2단계에서 방향 카드를 만들 때와 4-1에서 첫 화면을 짜기 전에 읽는다. 이 스킬로 만든 마케팅 쇼케이스 19종을 계측했더니(2026-10-06) 13종이 글·이미지 2단 분할이었고 타이포 단독·격자·콜라주는 0종이었다. 이 문서에 구현 절이 있는 히어로가 분할(§4-e) 하나뿐이었기 때문이다. 분할은 기본값이 아니다. 아래 여덟 가운데 하나다.
표의 순서에는 뜻이 없다. 고르는 기준은 "첫 화면의 주인" 열이다 — 브리프의 목표 행동과 실제로 가진 재료(사진, 수치, 제품, 작업 목록)가 주인을 정한다.
| 패밀리 | 첫 화면의 주인 | 맞는 브리프 조건 | 포기하는 것 | 좁은 화면에서 |
|---|---|---|---|---|
| 타이포 단독 | 글자 — 문장이나 수치 하나 | 문장·수치가 곧 제품이다(가격, 설치 명령, 선언). 쓸 만한 사진이 없다 | 한눈에 오는 분위기, 제품의 생김새 | 크기를 줄이기 전에 줄 수와 끊는 자리를 다시 정한다. 서너 줄이 되어도 된다 |
| 전면 이미지 | 사진·영상 한 장 | 장소·음식·공간처럼 보는 것이 곧 설득이다. 그 한 장을 실제로 갖고 있다 | 글의 양, 첫 화면 정보량 | 세로 크롭용 원본을 따로 둔다(images.md §2). 글은 사진 위 띠나 사진 아래로 간다 |
| 격자·목록 | 작업·상품·글 여러 점 | 포트폴리오·카탈로그·아카이브처럼 고를 대상이 여럿이고 하나를 대표로 세울 이유가 없다 | 한 가지 주장, 큰 표제 | 열 수를 줄이되 첫 화면에 두 점 이상 보이게 한다 |
| 글·이미지 분할 | 글과 이미지가 같은 무게 | 말로 한 것을 옆에서 바로 보여 줘야 한다. 설명과 증거가 같은 무게다 | 사진의 크기, 표제의 폭 | 위아래로 쌓인다. 무엇이 위인지 정한다. 구현 계약은 §4-e |
| 제품 단독 | 제품 한 점(누끼 또는 단색 배경) | 물건의 생김새가 사는 이유다. 배경 맥락이 필요 없다 | 사용 장면, 긴 설명 | 제품이 폭을 채우고 이름·가격은 아래 한두 줄 |
| 비대칭 콜라주 | 크기가 다른 조각 여럿의 겹침 | 캠페인·행사·에이전시처럼 한 장으로 요약되지 않는 것이 메시지다 | 읽는 순서의 명확함 | 겹침을 풀어 순서를 주고 조각 수를 줄인다. 겹친 채로 줄이면 글자가 가려진다 |
| 표제 + 전폭 무대 | 폭을 가로지르는 표제와 그 아래 전폭 데모·사진·도면 | 만져 보는 데모, 파노라마, 가로로 긴 도면·기록 | 첫 화면의 행동 버튼, 리드 문단의 길이 | 무대의 비율을 다시 정한다(가로 띠는 세로 화면에서 얇은 줄이 된다). 조작형이면 터치 조작을 따로 시험한다 |
| 데이터·도구가 주인 | 표·계산기·검색·예약 폼 | 방문자가 값을 찾거나 바로 일을 하러 온다(시세, 견적, 예약 가능 시간) | 브랜드 서사, 분위기 사진 | 도구가 먼저 오고 설명은 아래로 간다. 표는 component-systems.md §11-5 |
- 여덟에 없는 구도(가운데 한 단으로 읽는 문서형 등)도 브리프가 요구하면 쓴다. 같은 다섯 열로 적는다.
- 분할의 좌우만 바꾼 것은 같은 패밀리다.
- 분할을 고를 때는 다른 패밀리 하나를 같은 재료로 작게 스케치해 비교하고, 분할이 이긴 이유를
design.md에 한 줄 적는다. - 같은 저장소·세션의 직전 산출물과 같은 패밀리면 이유를 한 줄 적는다(art-direction.md §3-2 B-1의 구도 축).
- 초광폭(1920·2560)에서는 어느 패밀리든 §4-d를 따른다. 읽는 글은
--measure를 지키고, 넓어질 표면만 이름을 붙여 넓힌다.
히어로 글 묶음도 고른다
같은 계측에서 h1이 정확히 두 줄인 것이 18/19, h1 위에 작은 라벨을 둔 것이 16/19, 리드 문단이 19/19였다. "라벨 + 두 줄 표제 + 리드 문단 + 버튼 하나"가 한 벌로 굳었다. 네 요소는 각각 선택지다. 칸 안의 순서에는 뜻이 없다.
| 요소 | 선택지 | 무엇이 정하나 |
|---|---|---|
| 표제 위 라벨 | 없음 / 한 낱말 / 짧은 문장 | 표제만으로 무엇의 페이지인지 알 수 있으면 없앤다. 내비의 브랜드명을 되풀이하는 라벨은 뺀다 |
| 표제 꼴 | 한 줄 큰 낱말 / 두 줄 문장 / 서너 줄 긴 문장을 작게 / 표제는 작게 두고 제품·도구가 먼저(h1은 마크업에 남긴다) | 문장의 길이와 구도 패밀리. 줄 수를 맞추려고 문구를 늘리거나 줄이지 않는다 |
| 리드 | 없음 / 한 문장 / 사실 서너 줄(영업시간·가격·위치) | 표제가 다 말했으면 없앤다. 방문자가 찾는 것이 사실이면 문단보다 목록이다 |
| 행동 | 없음(다음 정보로 이어진다) / 버튼 하나 / 입력 한 칸 + 버튼 / 전화번호·주소 같은 직접 정보 | 브리프의 목표 행동. 행동이 없으면 억지로 넣지 않는다(art-direction.md §2) |
직전 산출물과 네 요소의 조합이 같으면 art-direction.md §3-2 B-2의 "히어로 글 묶음" 축에서 "같음"이다.
2-c. 내비 형태 — 상단 바는 여섯 가운데 하나다
같은 계측에서 19종 전부가 "브랜드 왼쪽 + 앵커 3~6개 오른쪽" 상단 바였다. 14종이 sticky였고 그중 11종이 backdrop-filter를 썼다. 이 문서에 다른 형태가 없었기 때문이다. 아래에서 고른다. 순서에는 뜻이 없다. 정하는 것은 페이지 길이, 목적지 수, 목표 행동, 차례 자체가 내용인지다.
| 형태 | 맞는 조건 | 좁은 화면에서 |
|---|---|---|
| 측면 색인 | 긴 문서·보고서·카탈로그처럼 지금 어디를 읽는지 알아야 한다 | 위쪽의 접는 차례 |
| 내비 없음 | 한 장짜리이고 섹션이 넷 이하, 행동이 하나다. 브랜드 표기와 행동 하나만 둔다 | 그대로 |
| 탭·세그먼트 | 같은 대상의 보기를 바꾼다(소비자/가맹, 월/연). 페이지 이동이 아니다 | 가로로 미는 탭 또는 선택 상자 |
| 상단 바(브랜드 + 항목) | 목적지가 3~6개이고 서로 다른 페이지·과업이다 | 라벨이 한 줄에 들어가면 그대로, 아니면 §4-b |
| 표제부에 녹인 차례 | 잡지·전시·행사처럼 차례가 곧 내용이다(전시실 3곳, 프로그램 5개) | 차례가 첫 화면의 목록이 된다 |
| 하단 고정 행동 바 | 모바일에서 예약·주문·전화가 목표다. 다른 형태와 함께 쓴다 | 안전 영역과 본문 가림을 확인한다(mobile-app-ux.md "safe area") |
- 상단 바를 고르면 왜 다른 형태가 아닌지 한 줄 적는다.
- 고정(sticky)과 흐림(
backdrop-filter)은 형태와 따로 정한다. 고정할 이유는 "긴 페이지에서 행동이나 현재 위치가 늘 보여야 한다"다. 화면 서너 개 길이의 페이지에는 그 이유가 없다. 흐림은 고정한 바가 사진·색면 위를 지날 때 글자를 읽히게 하는 수단이다. 바탕이 단색이면 불투명한 면으로 충분하다. - 한 페이지 안의 앵커만 모은 내비는 목적지가 아니라 차례다. 섹션이 넷 이하면 없애는 쪽을 먼저 본다.
- 어느 형태든
<nav>랜드마크, 키보드 순서, 현재 위치 표시는 accessibility.md를 따른다.
2-d. 푸터는 페이지의 마지막 행동이다
같은 계측에서 19종 중 14종의 푸터가 링크 0~1개의 한 줄 고지 띠였다. 푸터를 설계하지 않고 면책 문구 자리로만 썼다. 끝까지 읽은 사람은 관심이 가장 큰 방문자다. 그 사람이 다음에 할 일을 푸터가 준다.
담을 것은 브리프의 목표 행동과 방문자가 마지막에 찾는 사실에서 고른다: 주소·영업시간·전화 / 다음 행동(예약·구독·문의·내려받기) / 내비에 못 넣은 것까지 포함한 전체 차례 / 사업자 정보·약관·개인정보 처리방침 / 진실 고지(trustworthy-showcases.md §4-b). 고지는 그 안의 한 줄이지 푸터 전체가 아니다. 없는 주소·사업자번호를 지어내 채우지 않는다.
형태도 고른다. 순서에는 뜻이 없다. 위에서 고른 것 가운데 가장 중요한 하나가 형태를 정한다.
| 형태 | 맞는 조건 |
|---|---|
| 큰 워드마크 한 줄 | 브랜드를 한 번 더 남기는 것이 목적이고 담을 정보가 적다 |
| 연락처·영업시간 카드(지도와 함께) | 찾아가는 곳이다 — 가게·전시·사무실 |
| 고지 한 줄 띠 | 바로 위 섹션이 마지막 행동(문의·방문 안내)을 이미 끝냈다. 그 사실을 design.md에 한 줄 적는다 |
| 다음 행동 하나를 크게 | 페이지의 목표가 행동 하나다. 폼이나 큰 버튼이 푸터의 대부분을 차지한다 |
| 다열 색인 | 페이지가 여럿이고 내비에 다 넣지 못했다 |
화면이 여럿이면 내비와 푸터는 공통 셸이다(§4-m).
3. 타이포그래피
토큰은 3단계에서 정했다. 여기서는 적용이다.
폰트 자체의 로딩·기능·폴백은 references/typography.md 를 보라.
위계 단계는 표면이 정한다
마케팅·랜딩 표면은 디스플레이 / 섹션 제목 / 본문 3단계에서 시작한다. 문서·정책·도움말처럼 구조가 깊은 표면은 4~5단계가 필요하므로 레벨별 크기·굵기 토큰을 정의한다. 인접 레벨이 같은 크기를 공유하면 크기·굵기·색·여백 중 둘 이상의 차이로 구분한다(tokens.md "웨이트", typography.md §9-4). 새 크기를 즉석에서 만들지 말고 스케일에서 고른다.
본문이 주인공이다
헤드라인은 눈에 띄기 쉽다. 본문이 읽히는지가 실력이다.
- 한 줄 길이(
--measure)를 본문 역할에 적용하고, 실제 언어·글꼴·확대 상태에서 읽기 리듬을 검토한다. 전체 폭이 항상 실패인 것은 아니지만 긴 산문에 무심코 적용하지 않는다 - 문단 간격은 줄간격의 1.5배 이상. 들여쓰기와 문단 간격을 동시에 쓰지 마라
- 링크는 색만으로 구분하지 마라(밑줄 또는 다른 신호 병행)
한글이 들어가면
정본은 typography.md §6-5다. 값을 여기서 다시 정하지 않는다. 적용할 때 놓치기 쉬운 것만 적는다.
word-break: keep-all과overflow-wrap: anywhere를 짝으로 둔다- 본문 measure는
em으로 적는다.ch로 적으면 한글 한 줄이 절반이 된다 - 한글 이탤릭·한글 모노 라벨·넓은 양수 자간을 쓰지 않는다
- 표제는
text-wrap: balance, 마지막 줄 한 어절을 320·390·1440에서 확인한다
4. 반응형
브레이크포인트가 아니라 콘텐츠에서 시작한다
/* 나쁨: 기기 크기를 가정 */
@media (min-width: 768px) { }
/* 좋음: 콘텐츠가 깨지는 지점 */
.cards { grid-template-columns: repeat(auto-fit, minmax(18rem, 1fr)); }
auto-fit + minmax는 유동 반복 grid에 유용하다. 미디어쿼리와 container query는 구조·밀도·행동이 바뀌는 실제 실패 지점에서 선택한다. :has()와 CSS nesting은 2026-06에 Widely가 됐다 — 빌드 도구 없는 정적 CSS에서도 폴백 없이 쓴다. [WEB-BASELINE]
카드 안쪽 정렬선은 subgrid로 맞춘다
나란한 카드에서 제목이 한 줄인 카드와 두 줄인 카드가 섞이면 본문·가격·버튼의 시작선이 카드마다 어긋난다. 고정 높이나 줄 수 자르기로 맞추지 말고, 카드가 부모 그리드의 행 트랙을 물려받게 한다. subgrid는 Widely available(2026-03)이라 폴백 없이 쓴다 [WEB-BASELINE].
.card { display: grid; grid-template-rows: subgrid; grid-row: span 4; gap: var(--space-3); } /* 위 .cards 의 자식. 이미지·제목·본문·액션 4행 */
grid-row: span N의 N은 카드 안 직계 자식 수와 같아야 한다. 자식이 조건부로 빠지는 카드는 행 수가 어긋나므로 빈 자리 요소를 둔다. 실제 콘텐츠 길이로 320·768·1440 폭에서 시작선이 맞는지 확인한다.
입력 방식은 화면 크기와 별개 축이다
관찰 후보 — 구현 패턴. 터치스크린 노트북, 키보드가 달린 태블릿처럼 화면 크기만으로는 입력 방식을 알 수 없다. pointer·hover 미디어쿼리로 화면 크기 브레이크포인트와 별개 축으로 다룬다. 호버로만 접근되는 기능을 만들지 않는다는 원칙(아래 "모바일에서 먼저 확인해라") 자체는 이미 하드 게이트이고, 아래는 그 구현 패턴이다.
/* 정밀 포인터(마우스·트랙패드) */
@media (pointer: fine) {
.button { padding: 8px 16px; }
}
/* 거친 포인터(터치·스타일러스) — 데스크톱 폭에서도 나타날 수 있다 */
@media (pointer: coarse) {
.button { padding: 12px 20px; }
}
/* 호버를 지원하는 디바이스 */
@media (hover: hover) {
.card:hover { transform: translateY(-2px); }
}
/* 호버를 지원하지 않는 디바이스(터치) — active로 대체 */
@media (hover: none) {
.card { /* hover 상태 없음 */ }
}
프로젝트 계약 — 데스크톱을 고성능·비터치로 가정하지 않는다. 넓은 화면이 강력한 디바이스·마우스 전용을 뜻하지 않는다. 저사양 노트북, 터치스크린 노트북, 접근성 보조기기가 모두 데스크톱 폭에 있을 수 있다. pointer: coarse 대응과 성능 예산을 데스크톱 폭에서도 함께 시험한다. 성능 예산 자체는 뷰포트가 아니라 tokens.md §5를 그대로 따른다. [SKILL-IMPECCABLE]
컨테이너 쿼리 — 크기부터, 스타일은 조건부
관찰 후보 — 구현 기법. 컴포넌트 단위 적응에는 뷰포트 기준 media query보다 컨테이너 쿼리를 우선 검토한다. 카드는 뷰포트가 아니라 자신이 속한 컬럼에 반응해야 좁은 사이드바 안에서도 깨지지 않는다.
/* 크기 쿼리 — Baseline 2023-02 Newly, 2025-08경 Widely */
.card-list { container-type: inline-size; }
@container (max-width: 400px) {
.card { grid-template-columns: 1fr; }
}
/* 스타일 쿼리 — 2026-05 Newly(아직 Widely 아님), 폴백 경로를 함께 둔다 */
.theme-scope { container-type: inline-size; container-name: theme; }
@container theme style(--variant: compact) {
.card { padding: var(--space-2); }
}
크기 쿼리(@container 크기 조건)는 2023-02에 모든 주요 엔진이 지원해 Baseline Newly가 됐고 2025-08경 Widely가 됐다. 컨테이너 기준 단위(cqi)로 컴포넌트 안 유동 타입을 만들 수 있다 — 단, 글자 크기는 clamp(최소rem, …cqi, 최대rem)처럼 rem 하한을 두어 확대를 따라가게 한다. 반면 스타일 쿼리(style())는 2026-05-19부터 갓(Newly) 지원되어 아직 모든 브라우저에 널리 퍼지지 않았다 — 스타일 쿼리에 의존하는 레이아웃은 지원 확인 후 폴백을 함께 둔다. [WEB-BASELINE]
em·rem 브레이크포인트가 확대를 따라가는 이유
관찰 후보. px 단위 미디어쿼리는 사용자가 브라우저의 텍스트 확대(브라우저 줌이 아니라 base font-size 배율 조정)를 써도 반응하지 않는다. em·rem 단위 쿼리는 확대된 base font-size를 따라 브레이크포인트가 함께 움직인다. preflight.md가 이미 경고하는 "px 고정 폰트는 OS 확대를 게이트가 보장하지 못한다"는 문제의 해법 중 하나가 이것이다.
| 항목 | 단위 | 이유 |
|---|---|---|
font-size, max-width, 브레이크포인트, 스케일링 spacing |
rem |
확대된 base font-size에 반응해야 한다 |
| border, focus outline, box-shadow, 고정 장식 | px |
확대와 무관하게 일정한 두께를 유지해야 한다 |
이 표는 시작값이다. 프로젝트에 이미 다른 단위 관례가 있으면 그것을 따른다. [SKILL-BETTER-A11Y]
모바일에서 먼저 확인해라
데스크톱에서 아름다운 것이 모바일에서 무너지는 것이 기본이고, 그 반대는 드물다. 특히:
- 큰 타이포는 모바일에서 줄 수·가려짐·다음 행동의 가시성을 보고 크기·폭·문구·구도를 함께 조정한다. 단순 축소가 유일한 해법은 아니다
- 가로 스크롤이 생기는 요소를 찾아라 (
overflow-x: hidden으로 덮지 말고 원인을 고쳐라) - 터치 타깃은 과업 빈도·오입력 위험과 WCAG 2.2 AA의 24×24 CSS px 최소 기준(예외 포함)을 함께 검토한다. 시각 크기보다 히트 영역을 넓히는 구체 기법(의사요소 확장, 확장 영역 겹침 방지, 장식 레이어의
pointer-events)은 accessibility.md §8을 본다 - 호버로만 접근되는 기능을 만들지 마라
반응형 장면은 폭만 기록하지 않는다. 높이·종횡비·zoom·스크롤·상태가 짧은 화면, 긴 화면, 확대에서 결과를 바꾸는지 먼저 보고 대표 viewport를 고른다. 프로젝트의 특정 수치를 공통 규칙으로 만들지 않으며, 실제 측정 artifact와 실패 처리는 audit-gate.md의 렌더 측정 계약을 따른다.
4-b. 모바일 내비 — 햄버거를 기본값으로 삼지 마라
내비의 형태(데스크톱 포함)는 §2-c에서 먼저 고른다. 이 절은 고른 형태를 좁은 화면에서 구현하는 법이다.
내비 구조는 섹션 수가 아니라 대표 과업, 라벨 길이, 우선순위, locale, 화면 폭을 함께 보고 0단계에서 정한다. Hick의 연구를 항목 수 공식으로 쓰지 않는다.
| 관찰한 조건 | 검토할 구조 | 검증 |
|---|---|---|
| 모든 우선 행동이 한 줄에서 읽히고 hitbox가 확보됨 | 직접 노출된 내비 | 작은 폭·키보드에서 라벨 접힘과 순서를 본다 |
| 라벨이 길거나 행동 우선순위가 갈림 | 일부 노출 + 추가 메뉴 또는 하단 행동 | 자주 쓰는 행동의 탐색 시간·오입력을 본다 |
| 문맥별 명령이 많음 | 컨텍스트 메뉴·검색·계층적 IA | 메뉴 열기/닫기·포커스·Esc·현재 위치를 시험한다 |
항목 수 대신 실제 라벨·과업으로 결정한다
워드마크와 내비를 한 줄에 두면 둘 다 접힌다. 좁은 화면에서는 위아래로 쌓아라.
@media (max-width: 35em) {
.site-head { flex-direction: column; align-items: stretch; gap: var(--space-4); }
.nav { flex-wrap: nowrap; justify-content: space-between; letter-spacing: 0.04em; }
}
라틴 대문자 라벨의 자간(0.14em)이 좁은 화면에서 폭을 크게 먹는다(한글 라벨에는 애초에 0.1em 이상을 쓰지 않는다 — typography §6-5). 자간부터 줄여라 —
폰트 크기를 줄이는 것보다 읽기에 덜 해롭다.
단순 공개/접기에는 <details>를 검토한다
<details>는 간단한 공개/접기에는 유용하지만, 복잡한 메뉴의 포커스 이동·Esc·모달 동작을 자동으로 완성하지 않는다. 실제 키보드와 보조기술에서 목표 행동을 시험하고, 필요한 패턴을 선택한다.
<details class="nav-mobile">
<summary aria-label="메뉴">≡</summary>
<div class="nav-sheet"><a href="#a">…</a></div>
</details>
.nav-mobile > summary { list-style: none; cursor: pointer; }
.nav-mobile > summary::-webkit-details-marker { display: none; }
.nav-sheet { position: absolute; right: 0; }
여러 <details>에 같은 name을 주면 하나만 열리는 아코디언이 된다(Baseline Newly 2024-09). 펼침 전환은 ::details-content에 건다(세 엔진 지원, 미지원이면 즉시 열린다). [WEB-BASELINE]
아이콘이 세 줄(≡)일 필요는 없다. 에디토리얼이면 "메뉴" 라고 적는 편이 낫다 — 글자는 뜻이 분명하고, 세 줄은 학습된 관례일 뿐이다.
어느 쪽이든 검사는 같다
항목의 top 값이 한 종류여야 한다. 높이만 봐서는 못 잡는다(→ antipatterns.md).
new Set([...document.querySelectorAll('.nav a')]
.map(e => Math.round(e.getBoundingClientRect().top))).size === 1
4-c. 구조를 바꾸면 미디어쿼리도 같이 다시 써라
실측 사고. 히어로를 12칸 그리드에서 "글 + 사진 컨테이너" 2단으로 바꾸면서
좁은 화면 규칙을 그대로 뒀다. .hero-text 가 auto 1fr 2칸인데
세로 라벨을 가로로 눕히자 그 긴 문장이 auto 칸을 통째로 먹어 h1 칸이 짜부라졌고,
헤드라인이 390px 에서 6줄, 320px 에서 11줄이 됐다.
넓은 화면에서는 멀쩡했다. 구조 변경의 대가는 항상 좁은 화면에서 먼저 청구된다.
규칙: 그리드 구조를 바꿨으면 그 자리에서 320·390·768 을 다시 재라. 나중에 하면 원인이 어느 변경이었는지 못 찾는다.
4-d. 초광폭은 여백이 아니라 별도 레이아웃 상태다
1440px에서 멀쩡한 페이지가 1920·2560px에서 깨지는 방식은 반대다. 모바일처럼 넘치지는 않지만, 본문은 끝없이 늘어나고 고정 폭 헤드라인은 빈 여백에 밀려나며 표는 너무 좁아진다. 컨테이너 하나를 무조건 풀거나 조이지 마라. 읽는 표면과 비교·명세 표면의 요구가 다르다.
:root { --measure: 36em; /* 한글 본문 32~40자 시작(typography §6-5), 라틴 60~75자 — 실제 폰트로 글자 수를 센다 */ --container-wide: 74rem; }
.prose { max-width: var(--measure); }
.specification { width: min(100% - 2 * var(--pad-inline), var(--container-wide)); }
@media (min-width: 120rem) {
.specification { --container-wide: 86rem; } /* 데이터 표면만 확장 */
}
- 본문·설명·폼은
--measure를 지켜 행 길이와 label-입력 관계를 읽을 수 있게 둔다 - 표·도면·비교 작업대는 더 넓어도 되지만, 어떤 surface가 넓어지는지 이름으로 구분한다. 전역
max-width해제는 해결이 아니다 - 1920과 2560에서 h1 폭·히어로 칼럼·table/flex 셀의 실제 rect를 재고, 빈 공간이 필요한지와 내용 폭이 부족한지를 별도로 판정한다
Apple도 다양한 화면 크기에서 적응형 레이아웃과 읽기 좋은 텍스트 폭의 제한을 권한다. 여기의 정확한 수치는 제품 토큰으로 정하고, 넓은 폭을 시험하지 않은 상태를 통과로 부르지 않는다. Apple HIG Layout
4-e. 분할 히어로의 사진에는 상한이 있고, 카피에는 최소 폭이 있다
§2-b에서 "글·이미지 분할" 패밀리를 골랐을 때의 구현 계약이다. 분할이 히어로의 기본값이라는 뜻이 아니다 — 이 절이 유일한 히어로 구현 절이었던 탓에 쇼케이스 19종 중 13종이 분할로 모였다.
초광폭에서 1fr 1fr을 그대로 두면 사진은 창과 함께 계속 커지는데 카피는 상대적으로 가늘어져, 화면의 절반을 차지한 이미지와 읽을 수 없는 작은 설명이 공존한다. 풀블리드 편집 사진이 의도적으로 화면을 점유하는 경우가 아니라면, 분할 히어로를 wide surface처럼 별도 계약으로 둔다.
@media (min-width: 120rem) {
.hero { width: min(100%, var(--stage-wide)); margin-inline: auto; }
.hero-copy { width: min(100%, var(--copy-wide)); justify-self: end; }
.hero-media { width: min(100%, var(--media-wide)); justify-self: end; }
}
--stage-wide,--copy-wide,--media-wide, 후속--workbench-wide를 따로 이름 짓는다. 한--wrap을 무한히 키우지 않는다- 1920·2560에서 hero·copy·media·다음 정보 레일의 실제 rect를 기록한다. media의 상한, copy의 최소 폭, hero의 중앙/의도된 정렬을 그 페이지의 계약으로 단언한다
- 화면 맞춤 hero는 헤더가 overlay인지 문서 흐름에서 높이를 차지하는지 구분해 남은 높이를 계산한다. 카피·행동이 고정 높이보다 커지면 자연 확장하고, 모든 섹션에
100vh를 강요하지 않는다. 피사체·텍스트 안전 영역은overflow=0만으로 통과시키지 말고 짧고 긴 viewport의 실제 크롭과 인접 영역 겹침을 확인한다 - 이미지가 넓어질 자격은 이미지 자체의 편집적 역할에 있다. 정보·주문·비교가 뒤따르는 제품 화면에서는 후속 레일도 같은 밀도로 확장해, 첫 장면만 거대하고 본문은 점처럼 보이는 단절을 만들지 않는다
- 이 계약은 hero만의 면허가 아니다. 과정·추천·가맹 같은 별도 text-media split은 각각 원래의 padding/
max-width규칙을 가진다. wide에서 새 stage를 넣을 때는 inheritedpadding-inline의 양쪽 값과 상위 container selector의 specificity를 함께 확인한다. 한쪽에calc((100vw - --wrap) / 2)가 남으면 grid rect는 멀쩡해도 실제 텍스트 폭이 한 글자까지 붕괴할 수 있다. 각 split마다 heading의 실제 폭·줄 수와 copy/media rect를 별도로 측정한다.
이 규칙은 읽기 폭을 지키는 기존 wide-surface 원칙의 구체형이다. 사람의 시선은 이미지 크기보다 이미지·카피·행동의 비례에서 우선순위를 읽는다.
4-f. 데스크톱 어포던스 — 조건부
언제: 워크벤치·콘솔·전문가용 도구처럼 마우스·키보드 중심의 넓은 화면을 1차 대상으로 하는 프로젝트일 때만 연다. 일반 랜딩·마케팅 페이지에는 적용하지 않는다.
관찰 후보. 이런 프로젝트는 좁은 화면 패턴을 그대로 확대하지 않고, 넓은 입력 표면이 실제로 주는 것을 쓴다.
- 부가 정보용 hover 상태(단, 키보드·터치 대체 경로를 반드시 같이 둔다)
- 키보드 단축키(잦은 동작에)
- 우클릭 컨텍스트 메뉴
- 도움이 되는 곳의 드래그 앤 드롭
- Shift·Cmd로 다중 선택
- 여러 정보 패널을 동시에 노출(점진적 공개를 줄임)
이 목록은 워크벤치·콘솔의 시작값이지 일반 웹의 규범이 아니다 — 브리프가 다른 성격이면 열지 않는다. [SKILL-IMPECCABLE]
4-g. 모바일 표 — 카드 변환을 택했을 때의 구현
전략은 component-systems.md §11-5가 정한다. 여러 행을 열끼리 비교하는 과업(요금제·사양·성적)은 카드로 바꾸지 않고 가로 스크롤 + 첫 열 고정으로 간다. 이 절은 행 하나씩 훑는 과업이라 카드 변환을 택했을 때의 구현이다. 마크업·정렬 상태·밀도 같은 표 자체의 규칙도 그 문서 §11을 따른다.
관찰 후보 — 구현 패턴. display: block과 data-label 속성으로 각 셀을 헤더-값 쌍으로 보여주는 카드로 바꾸는 방법을 시작값으로 검토한다.
@media (max-width: 40rem) {
table, thead, tbody, th, td, tr { display: block; }
thead { display: none; }
tr { margin-block-end: var(--space-4); }
td {
display: flex;
justify-content: space-between;
padding-inline-start: 50%;
position: relative;
}
td::before {
content: attr(data-label);
position: absolute;
inset-inline-start: 0;
font-weight: 600;
}
}
<td data-label="가격">₩12,000</td>
변환 후에는 표의 의미(헤더-값 관계)가 스크린리더에서도 유지되는지 확인한다 — 시각적으로 카드가 되어도 마크업은 여전히 <table>이어야 관계가 보존된다. 다만 일부 브라우저는 표 요소의 display를 바꾸면 표 의미 자체를 접근성 트리에서 버린다. 변환할 때는 role="table"·rowgroup·row·columnheader·cell을 명시해 두고, 접근성 트리 스냅샷이나 스크린리더로 실제로 확인한다(accessibility.md §13). 레이아웃 검증(카드로 보이는지)과 의미 검증(관계가 읽히는지)은 별개이며 둘 다 확인한다. [SKILL-IMPECCABLE]
4-h. 전문가 도구는 밀도를 보존한다
언제: 프로젝트가 이미 확립된 밀도(콘솔, 관리자 도구, 데이터 그리드)를 갖고 있을 때.
관찰 후보. 컴팩트한 전문가용 도구는 히트 영역이 겹치지 않고 컨트롤이 구별되는 한, 위 "그루핑 도구" 절의 간격 시작값보다 적게 써도 된다. 확립되고 실제로 쓰이고 있는 밀도를, 이 문서의 시작값에 맞추려고 컨트롤을 키우거나 간격을 벌리지 않는다 — 이는 사용자·기존 토큰을 기본값보다 우선하는 원칙과 같은 방향이다. [SKILL-BETTER-LAYOUT]
4-i. 브레드크럼 — 조건부
언제: 3단계 이상의 깊은 계층 구조를 가진 콘솔·문서형·대시보드류 프로젝트일 때. 얕은 계층의 랜딩·포트폴리오에는 가치가 낮다.
관찰 후보. 깊은 계층에서 컨텍스트를 유지하는 수단으로 브레드크럼을 후보로 검토한다. 좁은 화면에서는 마지막 1~2단계만 보이거나 상위 단계를 축약 기호로 접는다. [SKILL-IMPECCABLE]
4-j. 방향 독립 레이아웃
언제: 프로젝트가 RTL 로케일(아랍어·히브리어 등)을 지원하기로 했을 때만 연다. RTL을 지원하지 않는 프로젝트에는 이 절이 적용되지 않는다 — icons.md §7의 아이콘 뒤집기 표가 이 절을 참조한다.
프로젝트 계약(조건부). 방향 의존 수평 위치는 물리 속성 대신 논리 속성으로 쓴다. dir="rtl"에서 레이아웃이 자동으로 미러된다.
| Physical | Logical |
|---|---|
margin-left |
margin-inline-start |
padding-right |
padding-inline-end |
left: 0 |
inset-inline-start: 0 |
text-align: left |
text-align: start |
border-right |
border-inline-end |
/* 좋음 — dir="rtl"에서 자동 미러 */
.section { padding-inline: var(--space-6); }
.section .child { margin-inline-start: var(--space-4); }
/* 나쁨 — RTL에서 깨짐 */
.section { padding-left: 24px; padding-right: 24px; }
.section .child { margin-left: 16px; }
노치 대응 같은 진짜 물리적 기하나 제스처 방향처럼 언어와 무관한 것에는 물리 속성을 그대로 남긴다.
진행 시퀀스는 미러되지만 손으로 배치한 요소는 아니다. 별점, 스텝 인디케이터, 진행 바처럼 배치가 진행을 인코딩하는 요소는 RTL에서 시퀀스가 미러되어 트레일링부터 채워진다. 논리 속성을 쓴 flexbox·grid는 자동으로 미러되지만, position: absolute 같은 수동 배치 요소는 미러되지 않는다는 함정이 있다 — 별도로 확인한다. 숫자 내부의 자리 순서는 방향과 무관하게 절대 뒤집히지 않는다.
읽기 순서는 좌우가 아니라 리딩→트레일링으로 사고한다. 위 §2 "표의 숫자는 끝에, 텍스트는 시작에 정렬한다" 절의 끝·시작도 이 어휘를 쓴다. [SKILL-BETTER-LAYOUT]
4-k. 가짜 현지화 검사
언제: 프로젝트가 다국어(번역)를 지원할 때.
프로젝트 계약(조건부). 번역된 문자열은 늘어나고, 짧은 문자열일수록 비율적으로 더 늘어난다 — 그래서 한 단어짜리 버튼 라벨이 화면에서 가장 위험하다. 문자열 확장 폭은 언어와 원문 길이별로 다르므로 하나의 퍼센트 예산에 의존하지 않는다. 배포 전 가짜 현지화(pseudo-localization, 문자를 늘리고 특수문자로 감싸 실제 번역 없이 레이아웃 반응을 시험하는 기법)나 대표 로케일 1개로 실제 테스트한다.
- 텍스트 컨테이너에 고정 폭·고정 높이를 두지 않는다(줄바꿈을 허용하거나
min-height를 쓴다). - 버튼은 라벨 길이에서 스스로 크기를 얻는다(
padding-inline, 하드코딩된 폭 금지). 텍스트 버튼은 줄바꿈을 허용하고,nowrap은 아이콘 전용 버튼이나 짧은 고정 라벨에만 쓴다.
/* 좋음 — 긴 번역이면 두 줄이 된다. 넘치지 않는다 */
.button { padding-inline: var(--space-4); min-inline-size: 6rem; }
/* 나쁨 — 독일어 등 긴 번역에서 잘린다 */
.button { width: 96px; overflow: hidden; }
[SKILL-BETTER-LAYOUT]
4-l. 숫자·날짜·통화는 로케일 API로 만든다
언제: 화면에 가격·수량·날짜·상대 시간이 나올 때. 단일 언어 사이트도 포함한다.
프로젝트 계약. 표시용 숫자·날짜 문자열을 손으로 조립하지 않는다("₩" + n.toLocaleString() 같은 조각 이어 붙이기 포함). Intl API에 로케일을 넘겨 만들고, 로케일은 <html lang>과 같은 값을 쓴다.
const locale = document.documentElement.lang || "ko-KR";
new Intl.NumberFormat(locale, { style: "currency", currency: "KRW" }).format(12000); // "₩12,000"
new Intl.NumberFormat(locale, { notation: "compact" }).format(12000); // "1.2만"
new Intl.DateTimeFormat(locale, { dateStyle: "long" }).format(new Date()); // "2026년 10월 2일"
new Intl.RelativeTimeFormat(locale, { numeric: "auto" }).format(-1, "day"); // "어제"
new Intl.PluralRules("en-US").select(2); // "other" — 영어 복수형 분기
- 한국어 축약은 만 단위("1.2만")다. 라틴식 "12K"를 한국어 화면에 쓰지 않는다.
notation: "compact"가 로케일에 맞게 처리한다 - 통화 기호와 "원" 표기 중 무엇을 쓸지는 카피 관례다(product-copy.md §11-1). 결정한 뒤에는 한 화면에서 섞지 않는다
- 숫자가 나열되면
tabular-nums(typography.md §5). 서버 렌더와 클라이언트 렌더의 로케일·시간대가 다르면 하이드레이션 때 문자열이 바뀌므로 시간대(timeZone)를 명시한다 - 출력 예시 문자열은 런타임 버전에 따라 조금 다를 수 있다. 정확한 문자열로 테스트를 고정하지 말고 실제 출력으로 확인한다
4-m. 화면이 여럿일 때 — 순서와 일관성
언제: 페이지·화면이 둘 이상인 작업. 가장 흔한 실패는 전 화면을 한 번에 만들고 마지막에 한꺼번에 감사하는 것이다 — 방향이 틀리면 전부 다시 한다.
- 목록부터. 화면·페이지 목록과 각 화면의 주 과업 한 줄을
design.md에 적는다. 목록에 없는 화면은 만들지 않는다. - 기존 화면을 고치는 작업이면 먼저 찍는다. 손대기 전에 화면마다 1440·390 캡처를 남긴다. 회귀 비교와 "아까 게 낫다"의 기준이 된다.
- 대표 화면 하나로 방향을 닫는다. 과업이 가장 무거운 화면(마케팅이면 홈, 제품이면 가장 자주 쓰는 화면) 하나를 감사까지 끝낸 뒤 나머지를 만든다.
- 공통 셸은 한 번 만든다. 헤더·내비·푸터·페이지 폭·섹션 간격은 대표 화면에서 정하고 나머지 화면이 그대로 쓴다.
- 고정하는 것과 화면마다 정하는 것. 고정: 토큰 전부, 공통 셸, 컴포넌트 변형, 용어. 화면마다: 섹션 구성, 구도, 밀도, 이미지 비중. 과업이 다른 화면(랜딩과 관리자)은 표현 강도를 따로 적는다(trustworthy-showcases.md).
- 감사 범위. 하드 게이트와 자동 검사는 모든 화면에 돈다. 시각 루브릭은 대표 화면과 과업이 다른 화면에 돌고, 같은 틀을 쓰는 화면(블로그 글, 설정 하위 탭)은 하나를 대표로 채점한 뒤 나머지는 캡처만 열어 본다. 무엇을 대표로 삼았는지 보고에 적는다.
5. 이 단계의 통과 조건 (구현 중 자기 점검 — 납품 판정은 preflight.md)
납품 판정은 preflight.md X13이 한다. 폭 320px 가로 스크롤은 W1, Tab 도달·포커스 링은 A1·A2, 토큰 위생은 M-D, 같은 행 액션 묶음 hitbox는 W9·G23(audit-gate-rules.md), 한글 조판은 P8과 typography.md §6-5가 판정한다. 여기서는 레이아웃을 짜는 동안 확인할 것만 둔다.
- CSS/HTML만으로 페이지가 완성됐다. JS를 꺼도 읽힌다
- 첫 화면 메시지의 수와 우선순위가 대표 과업에 맞다(셋은 점검 틀이지 상한이 아니다)
- 히어로 구도 패밀리(§2-b)·내비 형태(§2-c)·푸터 형태(§2-d)를 골랐고 이유가
design.md에 한 줄씩 있다 - 의미 역할이 다른 연속 섹션이 같은 거시 그리드를 3회 반복하지 않는다. 반복 데이터면 같은 구조를 유지한 이유가 적혀 있다
- 본문에
--measure가 적용됐다 - 320 외에 360(소형 안드로이드)·375/390(iPhone)도 본다
- 데스크톱 분할 패널을 숨긴 상태에서 빈 그리드 트랙이 남지 않고 주 표면이 전체 폭을 회수한다. 모바일 시트는 닫힘·배경·포커스 복원을 따로 검증한다
근거: 저장소 조사 문서 02-methodology, 03-trends-2026, 04-ai-slop-signatures (저장소 전용, 설치본 미포함, 조사일 2026-08-20)