Chapter 13

웹 실험실

열두 장 동안 책방 E의 새 웹사이트를 한 겹씩 정했다. 기준 폭 대신 최소 폭 320, 4의 배수 간격 여덟 단계, 12단 그리드와 최대 폭 1,200, em으로 적은 중단점 둘, 원시와 의미 두 층의 토큰, 버튼의 다섯 상태와 44 px 터치 타깃, 펼친 탭 바, 네 칸짜리 신청 폼, 움직임 두 단계, 접근성 점검표, 첫 화면 500 KB 예산. 그런데 이 값들은 각 장에서 따로 정했다. 글자를 두 배로 키우면 중단점이 밀리고, 칩이 커지면 한 줄에 들어가는 날짜가 줄고, 기기 픽셀 비율이 높으면 같은 사진도 더 큰 파일을 받는다. 이 장은 가상의 페이지 하나를 작업대에 올려 놓고 그 값들을 한꺼번에 움직이는 실험실이다. 실험실은 정답 설정을 알려 주지 않는다. 지금 설정에서 단이 몇 개인지, 글줄이 몇 자인지, 가장 작은 타깃이 몇 픽셀인지, 대비와 무게와 첫 화면이 뜨는 시간이 얼마인지를 숫자로 보여 주고, 휴대폰과 넓은 화면을 나란히 놓아 눈으로 고르게 한다.

실험실 사용법

실험실의 재료는 책방 E 새 사이트의 세 페이지다. 첫 화면(다음 모임 하나, 그 아래 행사 일정·이달의 책·신간), 행사 일정(날짜 칩 줄과 행사 카드), 모임 신청 폼(네 칸). 모두 이 책이 직접 그린 가상의 페이지다. 이 페이지는 진짜 웹 페이지가 아니라 캔버스 위에 그린 목업이다. 실험실 안에 장마다 정한 규칙을 단순한 계산으로 옮겨 두고(폭이 35 em을 넘으면 행 모양, 칩은 최소 44 px…), 설정이 바뀔 때마다 상자의 자리와 크기를 다시 계산해서 그린다. 그래서 진짜 브라우저와 몇 픽셀씩 다를 수 있지만, 무엇이 어디서 깨지는지는 같은 순서로 보인다.

설정은 여덟 갈래다. 화면 쪽에서는 기기 폭, 브라우저 확대(100·200·400%), 글자 크기 설정(100~200%). 배치 쪽에서는 짜임(고정 960 / 기기 중단점 768·1024 / em 중단점 35·56), 간격(첫 시안의 제각각 값 / 여덟 단계). 상호작용 쪽에서는 색 토큰, 포커스 표시, 부품 크기. 쓰임 쪽에서는 사진·글꼴·스크립트와 통신 조건이다. 각 갈래에는 장마다 처음 시도했던 값(첫 시안)과 서윤이 고른 값이 함께 들어 있다.

화면 2·3장 최소 폭 320 간격 4~64 배치 4·5·6장 12단·1,200·35/56em 토큰 두 층·rem 상호작용 7~10장 상태·44 px·탭 바 폼 네 칸·모션 쓰임 11·12장 점검표 여섯 줄 500 KB·LCP 2.5초 가상 페이지 첫 화면·일정·폼 재는 것 단 수·중단점글줄 한 줄 글자가장 작은 타깃글자·테두리 대비포커스 링 대비첫 화면 무게LCP(모형) 360 ↔ 1280 비교 숫자는 고르는 근거이지 답이 아니다
그림 1. 실험실의 흐름. 앞 장들에서 정한 값이 네 갈래로 들어가 가상의 세 페이지를 그린다. 실험실은 그려진 상자에서 단 수, 글줄, 타깃 크기, 대비, 포커스 링, 무게와 LCP를 재고, 같은 설정을 휴대폰 폭과 넓은 화면 폭에 나란히 놓는다.

먼저 실험실이 얼마나 다른 두 페이지를 그릴 수 있는지 보자. 아래는 같은 첫 화면을 폭 360 휴대폰에 띄운 두 모습이다. 왼쪽은 장마다 처음 시도했던 값을 한데 모은 판이다. 고정 960 짜임, 제각각인 간격, 연회색 #999999 보조 글자, 포커스 표시 없음, 120×36으로 못 박은 버튼, 원본 사진. 실제로 이런 판이 한 번에 있었던 것은 아니고, 각 장의 첫 시도를 모은 견본이다. 오른쪽은 각 장에서 서윤이 고른 값이다. 가운데 막대를 끌어 비교한다.

그림 2. 같은 첫 화면, 다른 규칙. 왼쪽(첫 시안 모음): 960 폭이 360 화면을 넘어 오른쪽이 잘리고(가로 넘침 —), 보조 글자 대비는 —이다. 오른쪽(서윤의 선택): 1단으로 쌓이고, 본문은 한 줄 약 —자, 아래 탭 바의 칸 폭은 — px다. 두 판 모두 이 장의 실험실로 그린 목업이다.

두 판은 한두 가지가 아니라 여덟 가지가 함께 다르다. 그래서 "무엇 때문에 좋아졌는가"를 말하기 어렵다. 이 장의 앞부분은 그 여덟 갈래를 하나씩 떼어 낸 패널이다(각 패널은 앞 장의 시뮬레이터를 실험실 형식으로 다시 쓴 것이다). 마지막 절의 종합 실험실이 다시 모두를 한데 모은다.

목업은 브라우저가 아니다 실험실의 페이지는 브라우저가 실제로 배치한 HTML이 아니라, 이 장의 스크립트가 규칙을 계산해 캔버스에 그린 그림이다. 글줄의 한 줄 글자 수는 한글 산문의 평균 글자 폭을 0.757 em으로 친 어림이고(5장에서 28 em을 약 37자로 친 것과 같은 비), 화면 높이는 폭에서 어림한 값이다. 무게와 LCP는 12장의 단순 모형을 그대로 썼다. 실제 기기에서는 주소창 높이, 글꼴, 브라우저마다 다른 확대 방식 때문에 값이 달라진다. 실험실은 출발값을 고르고 부딪치는 곳을 찾는 도구이고, 마지막 확인은 진짜 휴대폰과 진짜 브라우저에서 한다.

폭 패널: 창 폭과 글자 크기가 짜임을 바꾼다

첫 패널은 2장과 5장을 한데 모은 것이다. 페이지의 짜임을 정하는 폭은 기기의 화면 폭이 아니라 CSS 뷰포트 폭(CSS viewport width)이다. 브라우저를 200%로 확대하면 같은 노트북 화면 1,280 px이 CSS로는 640 px이 된다. 그래서 확대는 화면을 좁게 만든다. 반면 휴대폰의 글자 크기 설정은 뷰포트 폭을 그대로 두고 rem의 크기만 키운다. em으로 적은 중단점은 rem을 따라 밀린다. 글자를 150%로 키우면 35 em 중단점은 560이 아니라 840 px에 선다.

아래 목업의 오른쪽 끝을 끌거나 기기 폭 막대로 폭을 바꾼다. 맨 아래 눈금자에는 지금 글자 크기에서의 두 중단점이 표시된다. 짜임을 "기기 중단점 768·1024"(5장에서 지후가 처음 넣은 값)로 바꾸면 중단점은 글자 크기와 상관없이 제자리에 있다.

SIMULATOR

폭 패널: 같은 첫 화면, 폭과 글자 크기

목업의 오른쪽 끝을 끈다
CSS 뷰포트 폭—
짜임—
중단점(px)—
본문 한 줄—
신간 한 줄—
해볼 것: ① 짜임을 "기기 중단점"으로 두고 폭을 700 근처로 맞춘다. 1단 카드가 화면 끝까지 늘어나 본문 한 줄이 40자를 넘는다(5장에서 지후가 창을 줄이다 본 장면). ② "em 중단점"으로 바꾼다. 560에서 이미 행 모양이 되고 본문은 28 em(약 37자)에서 멈춘다. ③ 폭을 1,280에 두고 글자 크기를 200%로 올린다. em 중단점은 1,120·1,792로 밀려 넓은 화면에서도 1단·탭 바 짜임이 되고, 기기 중단점은 넓은 짜임 그대로 글자만 커진다. 어느 쪽 글줄이 덜 무너지는지 본다. (모형의 가정: 한 줄 글자 수 = 글줄 폭 ÷ (0.757 × 글자 크기), 화면 높이는 폭에서 어림. 본문 크기는 6장의 clamp() 식, 신간 카드는 4장의 minmax(240px, 1fr).)

기기 중단점 짜임은 세 견본 폭(360·768·1280)에서는 멀쩡하지만 그 사이 폭에서 깨진다. em 중단점 짜임은 폭이 아니라 "글줄이 몇 자가 되는가"를 따라 짜임을 바꾸므로, 글자를 키운 사람의 화면에서도 같은 기준이 지켜진다. 대가도 있다. 글자를 200%로 키운 노트북에서는 넓은 화면인데도 아래에 탭 바가 붙는다. 서윤과 지후는 이것을 "글자를 키운 사람에게는 휴대폰 짜임이 더 읽기 좋다"는 뜻으로 받아들였다. 5장에서 도경이 했던 말과 같다.

간격·그리드 패널: 숫자가 아니라 역할로

3장의 간격 단계(4·8·12·16·24·32·48·64)와 "안쪽 간격 < 바깥 간격" 규칙, 4장의 12단·거터 24를 행사 일정 페이지 위에 겹쳐 본다. 분홍 막대는 간격, 붉은 띠는 단이다. 간격을 "첫 시안"으로 바꾸면 3장에서 지후가 재 본 제각각의 값(9, 11, 15, 17, 22, 27)이 들어간다.

그리드 단 수는 이 장에서 처음 정하는 값이 하나 있다. 4장은 넓은 화면의 12단만 정했고 좁은 화면의 단은 남겨 두었다. 실험실은 5장의 두 중단점에 맞춰 좁은 쪽부터 4단(거터 16) → 8단(거터 24) → 12단(거터 24)을 안내선으로 그린다. 4·8·12는 모두 신간 카드 수(1·2·3·4권)를 나누어떨어지게 하는 수다.

SIMULATOR

간격·그리드 패널: 행사 일정 페이지

쓰인 간격 값—
안 < 밖 규칙—
그리드—
해볼 것: ① "첫 시안"에서 구획 제목 아래 간격(22)과 카드 사이(17)를 비교한다. 제목이 자기 목록보다 위쪽 여백에 더 가까워 보인다. ② "여덟 단계"로 바꾼다. 제목 → 목록 16, 카드 사이 24, 구획 사이 48로 무리가 갈린다. ③ 폭을 1,440까지 넓힌다. 900 근처에서 8단이 12단으로 바뀌고 오른쪽에 달력 칸이 생긴다. 카드 경계가 단 선 위에 서는지 본다. (모형의 가정: 간격은 rem이라 글자 크기 설정을 따라 커지고, 화면 가장자리 여백 16만 px. 단 수는 이 장이 정한 4·8·12 안내선.)

"첫 시안"과 "여덟 단계"의 차이는 숫자가 깔끔해졌다는 것이 아니다. 3장에서 서윤이 겪었듯, 첫 시안의 값을 가장 가까운 단계로 반올림하기만 하면 화면은 오히려 어수선해진다. 실험실의 규칙 검사는 값이 단계에 있는지가 아니라, 무리 안 < 카드 안 < 카드 사이 < 구획 사이의 순서가 지켜지는지와 구획 제목이 자기 목록에 붙는지를 본다. 규칙을 깨는 곳은 언제나 있을 수 있다. 4장의 "이달의 책" 띠는 일부러 그리드를 깨고 화면 끝까지 간다. 깨는 곳은 하나로, 이유와 함께 적는다.

토큰·대비 패널: 의미 토큰 하나를 바꾸면

6장의 의미 토큰(bg, surface, text, text-muted, action, on-action, band, on-band)으로 첫 화면을 칠하고, 그 페이지에 실제로 쓰인 글자–바탕 짝을 모두 ART.contrast(WCAG 2 대비율)로 잰다. 큰 글자(24 px 이상, 또는 약 18.7 px 이상 굵게)는 3:1, 나머지 글자는 4.5:1, 입력 칸 테두리 같은 비텍스트 요소는 3:1이 바닥이다(WCAG 2.2의 1.4.3, 1.4.11).

색 토큰은 넷이다. "옛 색"은 6장에서 서윤이 찾아낸 연회색 #999999가 섞인 상태, "밝은 토큰"과 "어두운 토큰"은 6장의 표, "반전 다크"는 지후가 처음 해 본 OKLCH 명도 뒤집기다. 아래 "주색 명도" 막대는 의미 토큰 action 하나의 OKLCH 명도만 움직인다. 버튼의 바탕, 링크, 눈썹 글이 모두 이 토큰을 부르므로 한 곳을 바꾸면 그 색을 쓰는 모든 짝의 대비가 함께 바뀐다.

SIMULATOR

토큰·대비 패널: 첫 화면의 글자–바탕 짝

짝(쓰인 곳)글자바탕대비(기준)
해볼 것: ① "옛 색"에서 가장 낮은 짝을 찾는다. 날짜 줄의 #999999가 2.85, 바닥글의 차분한 초록이 2.62다. ② "반전 다크"로 바꾼다. 주색 버튼과 흐린 글자가 3.19·3.06으로 떨어진다(6장에서 지후가 본 숫자). ③ "밝은 토큰"에서 주색 명도를 +0.08쯤 올린다. 버튼 위 흰 글자가 4.5 아래로 내려가는 순간을 본다. 어두운 토큰에서 같은 막대를 내리면 반대 방향으로 무너진다. (모형의 가정: 대비는 이 서가의 엔진 ART.contrast, 명도 조정은 OKLCH에서 L만 바꾸고 색역 밖이면 채도를 줄인다.)

토큰 체계가 하는 일은 "같은 초록이 여섯 가지 hex로 흩어지지 않게" 하는 것만이 아니다. 실험실처럼 의미 토큰 하나를 바꾸었을 때 영향을 받는 짝이 모두 한 표에 모인다는 것이 더 크다. 6장의 다크 모드 값(바탕 #151917, 주색 #5eaa85, 주색 위 글자 #151917)은 반전이 아니라 따로 지정한 값이라, 표의 모든 짝이 4.5를 넘는다. 가장 낮은 짝은 카드 표면 #2a2f2d 위의 주색 날짜 줄 4.90이다. 6장이 바탕 위에서 잰 6.39보다 낮은 것은 카드가 바탕보다 한 단계 밝기 때문이다. 토큰 표는 짝마다 "어느 바탕 위에서"까지 적어야 한다.

포커스 패널: 장과 코드가 다르게 적은 링

장마다 정한 값과 지후가 따로 짠 시험판 코드가 다르게 적은 곳이 나왔다. 포커스 표시다. 7장은 버튼 상태 표에 3 px 링 #22342a, 2 px 띄움, 어두운 영역 안에서는 크림 #ffe2d7을 적었고, 11장의 점검표도 같은 링을 불렀다. 그런데 시험판 코드에는 개발 템플릿에서 온 3 px 책방 초록 #2f7d5b, 2 px 띄움이 남아 있었다. 둘 다 흰 바탕에서는 기준을 넘는다. 차이는 바탕이 바뀔 때 드러난다.

아래 패널은 신청 폼과 짙은 바닥글을 Tab 순서대로 한 칸씩 옮겨 가며, 링과 그 링이 닿는 바탕의 대비를 잰다. 포커스 링은 비텍스트 요소이므로 바탕과 3:1 이상이어야 알아볼 수 있다(1.4.11). WCAG 2.2의 2.4.13 포커스 모양(AAA)도 포커스 표시가 그 둘레와 3:1 이상 대비를 갖기를 요구한다.

SIMULATOR

포커스 패널: Tab으로 한 칸씩

지금 포커스—
링 – 바탕 대비—
모든 정류장 최저—
해볼 것: ① "시험판: 초록 링"으로 두고 Tab 순서를 끝까지 민다. 마지막 정류장인 바닥글의 "오시는 길"에서 링 대비가 2.64로 떨어진다. 짙은 바닥글 위의 초록 링은 거의 보이지 않는다. ② "7장: 짙은 링 + 크림"으로 바꾼다. 흰 칸에서는 13.19, 바닥글에서는 크림 링이 10.74다. ③ "어두운 토큰으로 보기"를 켠다. 짙은 링 규칙은 바탕이 어두우면 크림으로 바뀌므로 그대로 버틴다. ④ "표시 없음"은 대비를 잴 것조차 없다. (모형의 가정: 링은 요소에서 2 px 띄운 3 px 선이고, 대비는 링과 그 아래 바탕 사이로 잰다. 이중 링은 안쪽 흰 2 px와 바깥 짙은 2 px 중 대비가 큰 쪽.)

실험실에서 정한 것은 이렇다. 포커스 색은 7장의 짙은 링 #22342a로 한다. 흰 바탕 13.19, 페이지 바탕 #f6fcf8 12.68, 크림 띠 10.74다. 어두운 영역(바닥글, 그리고 다크 모드의 모든 바탕) 안에서는 크림 #ffe2d7 링을 쓴다. 바닥글 위 10.74, 다크 모드 바탕 위 14.46이다. 시험판의 초록 링은 흰 바탕 4.99, 크림 띠 4.07로 밝은 곳에서는 통과하지만, 짙은 바닥글 위에서 2.64로 무너진다. 주색 버튼 둘레에서도 링과 버튼이 같은 색이라, 그 사이의 2 px 틈이 아니면 한 덩어리로 보이기 쉽다. 11장의 점검표(키보드만으로 신청, 포커스 순서 = 읽는 순서, :focus-visible, 고정 띠만큼 scroll-padding)는 그대로 두고 시험판 코드의 색 한 줄만 7·11장 값으로 맞춘다.

:root { --focus-ring: #22342a; } /* 밝은 바탕 위 13.19 · 12.68 · 10.74 */ .on-dark, [data-theme="dark"] { --focus-ring: #ffe2d7; } /* 짙은 바탕 위 10.74 · 14.46 */ :focus-visible { outline: 3px solid var(--focus-ring); outline-offset: 2px; }
문서와 코드가 다르게 적었을 때 지후는 회사에서 이런 일을 자주 본다고 했다. 시안 파일에 하나, 컴포넌트 문서에 하나, 점검표에 하나. 셋이 다르면 결국 코드에 들어간 값이 이긴다. 그래서 지후의 팀은 "값은 한 곳에만 적고, 다른 문서는 그 이름을 부른다"는 규칙을 둔다. 7장의 표와 11장의 점검표, 그리고 코드가 모두 hex 대신 var(--focus-ring)을 불렀다면 값은 처음부터 갈라질 수 없었다. 토큰 체계(6장)가 색과 글자에만 쓰는 도구가 아니라는 뜻이다. 보조 글자도 같은 일이 있었다. 6장과 11장은 text-muted를 #567666으로 적었는데, 시험판의 폼 코드에는 안내 글자가 #5d6b63으로 남아 있었다. 실험실에서는 토큰 하나(text-muted #567666, 흰 칸 5.03 · 바탕 4.83)로 합쳤다. 이 책의 엔진으로 다시 재면 #5d6b63은 흰 바탕 위 5.60이다. 둘 다 4.5를 넘으니 어느 쪽을 골라도 기준은 지키지만, 같은 일을 하는 글자가 두 색일 이유는 없다.

타깃 패널: 44는 글자를 키워도 44인가

7장은 터치 타깃의 사이트 최소를 44×44 CSS px로 정했다(WCAG 2.2 2.5.8의 24×24는 하한). 8장은 탭 바 네 칸이 폭 320·글자 200%에서도 칸 폭 80 px, 높이 56 px 이상이 되게 했다. 이 패널은 행사 일정 페이지를 휴대폰 폭에 놓고 모든 타깃에 상자를 그린다. 초록 실선은 44 이상, 주황 점선은 24~44, 빨강은 24 미만이다. 24 미만인 타깃에는 2.5.8의 간격 예외를 확인하는 지름 24의 원을 그린다.

목업의 타깃을 누르면, 7장의 손가락 흩어짐 모형(걸으면서 한 손, 표준편차 2.0 mm)으로 그 타깃을 노린 마흔 번의 터치를 찍는다. 노린 것에 닿으면 짙은 초록, 이웃 타깃에 닿으면 빨강, 빈 곳이면 회색이다.

SIMULATOR

타깃 패널: 휴대폰의 행사 일정

타깃을 눌러 노린다
가장 작은 타깃—
2.5.8 · 44 규칙—
칩 잘못 누르기(걷기)—
탭 바 칸—
해볼 것: ① "첫 시안"에서 날짜 칩을 눌러 본다. 28 px 칩에 간격 4라 걸으면서 누르면 열에 한 번쯤 옆 날짜에 닿는다(모형 약 11%). ② "서윤의 선택"으로 바꾼다. 칩 44·간격 8에서 잘못 누르기가 2% 안팎이 된다. 일주일이 한 줄에 다 들어가지 않아 끝 칩이 잘려 보이는 것은 7장에서 일부러 정한 단서다. ③ 글자 크기를 200%로 올린다. 첫 시안의 고정 버튼은 "신청하기" 글자가 잘리고 칩 글자가 넘친다. 서윤의 칩은 글자를 따라 75 px까지 커지고, 탭 바 글자는 두 줄로 꺾이며 칸이 높아진다. ④ 폭을 320으로 줄인다. 탭 바 칸은 80 px이다. (모형의 가정: 누른 자리는 노린 타깃 가운데를 중심으로 가로세로 독립인 정규 분포, 표준편차 2.0 mm, 1 CSS px ≈ 0.16 mm. 7장과 같은 교육용 가정이다.)

44라는 수는 "최소 높이"로 적어야 지켜진다. 첫 시안의 버튼처럼 height: 36px로 못 박으면 글자를 키운 사람에게 글자가 잘리고, 반대로 칩을 28px로 못 박으면 글자는 넘치고 타깃은 그대로 작다. 서윤의 부품은 min-height: 44px에 안쪽 여백과 rem 글자를 더해 크기가 내용을 따른다. 대가는 자리다. 글자 200%의 휴대폰에서 칩은 한 화면에 세 개 남짓만 보인다. 서윤은 칩을 줄이지 않고 가로로 넘기는 방식을 지켰다. 2.5.8은 크기를 간격으로 메우는 길도 열어 두었지만(24 원이 겹치지 않으면 통과), 간격은 잘못 누르기를 "반응 없음"으로 바꿀 뿐 노린 칩에 닿는 비율을 올리지는 않는다.

용량 패널: 픽셀 비율 3의 휴대폰은 어느 사진을 받는가

12장은 첫 화면(휴대폰)의 무게 예산을 500 KB로 정했다. HTML·CSS 50, 자바스크립트 50, 웹폰트 200, 첫 사진 150, 기타 50. 사진은 480·800·1600 세 폭의 WebP를 srcset으로 두고, "360 px 휴대폰은 800화소(약 75 KB)를 받는다"고 적었다. 그런데 브라우저가 고르는 파일은 화면의 CSS 폭이 아니라 그 폭에 기기 픽셀 비율(DPR)을 곱한 화소 수를 기준으로 한다. 서윤의 휴대폰은 DPR 3이다(2장).

아래 패널은 첫 화면 사진이 차지하는 CSS 폭과 DPR로 srcset이 고를 파일을 정하고, 12장의 내려받기 모형(대역폭과 왕복 지연, 동시에 받는 파일이 대역폭을 나눠 쓰는 단순 모형)으로 LCP를 어림한다. 1200 폭은 이 장에서 새로 넣어 보는 후보다. 무게는 12장과 같은 어림(면적이 4분의 1이면 무게는 약 3분의 1)으로 약 142 KB, 480 폭은 약 33 KB로 쳤다.

SIMULATOR

용량 패널: 첫 화면 무게와 LCP

고른 사진—
첫 화면 무게—
LCP 느린 모바일(모형)—
LCP 보통 LTE(모형)—
해볼 것: ① "srcset 3폭"·DPR 3·폭 360에서 고른 사진을 본다. 사진이 차지하는 폭 328 px × 3 = 984화소라 800으로는 모자라 1600(220 KB)을 받는다. 무게는 예산 500을 넘고 느린 모바일 LCP는 3초다. ② DPR을 2로 낮춘다. 656화소면 800(75 KB)으로 충분하다. 12장이 적은 대로 800화소는 DPR 2의 값이다. ③ "srcset 4폭"으로 바꾸고 DPR 3으로 돌린다. 1200(약 142 KB)을 받아 무게 492 KB, LCP 약 2.3초로 예산 안에 든다. ④ 글꼴을 "통짜"로 바꾸면 사진과 대역폭을 나눠 써서 LCP도 함께 늦어진다. (모형의 가정: 12장의 내려받기 모형을 그대로 썼다. HTML 15, CSS 35, JS 50, 기타 50 KB, 느린 모바일 1.6 Mbps·왕복 150 ms, 보통 LTE 10 Mbps·60 ms. 사진 무게 480·1200은 이 장의 어림. 브라우저는 실제로 캐시와 데이터 절약 설정에 따라 더 작은 파일을 고르기도 한다.)

이 패널이 찾아낸 것은 두 장 사이의 빈틈이다. 12장의 예산표는 맞고, 5장의 srcset도 맞다. 다만 12장은 기기 픽셀 비율 2의 휴대폰으로 계산했고, 서윤의 휴대폰 같은 DPR 3은 아무도 따져 보지 않았다. 고칠 길은 셋이다. 사진 폭 후보를 하나 더 두거나(1200), sizes를 실제보다 작게 적어 브라우저를 속이거나, 첫 사진의 예산을 늘리고 다른 갈래를 줄이는 것. 두 번째는 화면이 흐려지는 것을 받아들이는 길이고, 세 번째는 웹폰트 200 KB(TypeBook 11장의 예산)를 건드려야 한다. 서윤은 첫째를 골랐다. 파일이 하나 늘 뿐 화면도 예산도 지킨다.

규칙이 부딪치는 곳

각 패널은 한 갈래씩 떼어 보았다. 그런데 실험실의 진짜 쓸모는 갈래 사이에서 나온다. 한 장에서 정한 규칙이 다른 장의 규칙과 같은 화면에서 만날 때다. 서윤과 지후가 실험실을 돌리며 찾은 부딪침을 그림 3에 모았다. 어느 것도 한쪽이 "틀린" 것이 아니다. 각 장은 자기 문제를 풀었고, 두 해법이 함께 놓이는 조건을 그 장에서는 볼 수 없었을 뿐이다.

부딪친 두 규칙실험실의 결정 7장 링 #22342a흰 바탕 13.19 코드의 링 #2f7d5b바닥글 위 2.64 짙은 링, 어두운 곳은 크림최저 10.74 6장 #567666text-muted 폼 코드 #5d6b63폼 안내 글자 토큰 하나로 합침흰 칸 5.03 · 바탕 4.83 5·12장 srcset480·800·1600 DPR 3 휴대폰984화소 필요 1200 폭을 더함492 KB · LCP 2.3초 7장 칩 최소 44글자 따라 커짐 2장 폭 320+ 글자 200% 가로 넘김 유지한 화면 칩 약 3개 5장 em 중단점56em = 1,792 8장 넓은 화면 메뉴글자 200% 노트북 탭 바 짜임을 받아들임글줄 약 22자 유지
그림 3. 규칙이 부딪치는 다섯 곳. 왼쪽 두 칸은 서로 부딪친 두 규칙(장에서 정한 값이나 시험판 코드), 오른쪽은 실험실에서 고른 해법이다. 대비와 무게, 시간은 이 장의 실험실(엔진 ART.contrast와 12장의 모형)로 잰 값이다.

부딪침을 푸는 순서는 서윤이 정했다. 첫째, 사람이 못 쓰게 되는 쪽을 먼저 막는다. 바닥글 위에서 사라지는 포커스 링은 키보드 사용자에게 길을 끊는다. 둘째, 기준을 넘는 두 값 사이에서는 체계가 단순한 쪽을 고른다. 보조 글자는 둘 다 4.5를 넘으므로 토큰 하나로 합쳤다. 셋째, 비용이 작은 쪽으로 고친다. 사진 폭을 하나 더 뽑는 것은 웹폰트 예산을 깎는 것보다 싸다. 넷째, 어느 쪽도 고치지 않는 것도 선택이다. 글자를 200%로 키운 노트북에 탭 바가 붙는 것은 받아들였다. 그 사람에게 글줄 22자의 1단 짜임이 더 읽기 좋기 때문이다.

종합 실험실

이제 모든 설정을 한 작업대에 모은다. 위쪽에 목업이 놓이고, 아래 조절기가 여덟 갈래를 모두 움직인다. "나란히 비교"를 켜면 같은 설정을 폭 360 휴대폰과 폭 1,280 넓은 화면에 나란히 그리고, 점검표도 두 칸으로 나뉜다. 두 단추로 "첫 시안 모음"과 "서윤의 선택"을 한 번에 불러올 수 있다. 둘 다 출발점일 뿐이니 하나씩 바꿔 가며 어느 갈래가 어느 줄을 빨갛게 만드는지 본다.

순서는 이렇게 권한다. ① 페이지와 폭을 정한다. ② 확대 200%와 글자 200%를 차례로 켜서 짜임이 무너지지 않는지 본다(1.4.4, 1.4.10). ③ 그리드를 겹쳐 카드 경계가 단 선 위에 서는지 본다. ④ 색 토큰을 밝은·어두운으로 바꿔 대비 줄을 확인한다. ⑤ Tab 순서를 끝까지 밀어 포커스 링이 모든 바탕에서 보이는지 본다. ⑥ 타깃을 겹쳐 가장 작은 것을 찾는다. ⑦ 사진·글꼴·통신을 바꿔 무게와 LCP를 본다. ⑧ 나란히 비교로 휴대폰과 넓은 화면을 함께 본다.

SIMULATOR

웹 실험실

페이지와 화면

배치

상호작용

쓰임

점검지금 화면기준
해볼 것: ① "첫 시안 모음"을 누르고 폭 360에서 점검표를 위에서부터 읽는다. 가로 넘침 600 px, 글자 대비 2.62(바닥글), 포커스 없음, 타깃 24(메뉴), 무게 6.4 MB, LCP 30초 넘게(모형). 갈래 하나씩을 서윤의 값으로 바꾸며 어느 줄이 초록이 되는지 본다. ② "서윤의 선택"에서 확대를 400%로 올린다. 1,280 노트북이라면 CSS 폭 320이 되어 휴대폰 짜임이 된다(1.4.10). ③ 글자 크기 200%, 페이지 "신청 폼", 폭 320. 칸 높이와 버튼이 글자를 따라 커지고 오류 문장이 두세 줄로 꺾여도 잘리지 않는다. ④ 나란히 비교를 켜고 색 토큰을 "어두운 토큰", 포커스를 "초록 링"으로 바꾼 뒤 Tab 순서를 끝까지 민다. ⑤ 사진을 "srcset 3폭"으로, DPR을 3으로 두고 첫 화면의 무게 줄을 본다. (모형의 가정: 앞 패널들과 같다. 본문 크기·제목 크기는 5·6장의 clamp() 식, 중단점은 rem 기준 em, 간격은 rem, 화면 가장자리 16 px, 한 줄 글자 수는 0.757 em 어림, LCP는 12장 모형에 첫 화면에서 가장 넓게 보이는 요소를 LCP 후보로 쳤다.)

점검표에서 가장 먼저 볼 줄은 가로 넘침과 포커스 표시다. 둘은 실패하면 어떤 사람에게는 페이지 전체가 쓸 수 없게 된다. 대비와 타깃은 그다음이다. 무게와 LCP는 통신 조건에 따라 오가므로 "느린 모바일"에서 기준을 넘는지를 본다. 표의 어느 줄도 빨갛지 않다고 좋은 페이지가 되는 것은 아니다. 숫자가 말하지 못하는 것, 곧 첫 화면이 "다음 모임" 하나를 제대로 말하는지, 책방의 목소리처럼 보이는지는 두 폭을 나란히 놓고 눈으로 고른다.

자동 점검이 잡지 못하는 것 접근성 자동 검사 도구는 대비, 대체 텍스트의 유무, 라벨 연결 같은 기계가 셀 수 있는 것을 잘 찾는다. 반면 대체 텍스트가 사진을 제대로 말하는지, 포커스 순서가 읽는 순서와 맞는지, 오류 문장이 고칠 방법을 알려 주는지는 사람이 판단해야 한다. W3C의 WCAG 2.2 이해 문서(Understanding WCAG 2.2)도 여러 성공 기준에 대해 사람의 판단이 필요하다고 설명한다. 이 실험실의 점검표도 마찬가지다. 대비와 타깃 크기는 계산하지만, "img_0231"을 알아내지는 못한다. 그래서 11장의 공개 전 세 가지(키보드만으로 신청, 낭독기로 제목·링크 목록 듣기, 200% 확대)는 실험실 점검과 따로 남긴다.
종이 휴대폰 세 장 두꺼운 종이를 실제 크기로 오린다. 휴대폰(약 7×15 cm), 태블릿(약 15×20 cm), 노트북 화면(약 30×19 cm). 실험실에서 "서윤의 선택"으로 첫 화면을 세 폭(360·768·1280)에 띄워 캡처하고, 연필로 각 종이에 같은 비율로 옮겨 그린다. 상자와 글줄만 그리면 된다. 그다음 휴대폰 종이를 손에 들고 엄지손가락 끝에 물감이나 인주를 묻혀, 걸으면서 "신청하기"와 날짜 칩을 다섯 번씩 찍어 본다. 찍힌 자국이 상자 밖으로 얼마나 나갔는지 자로 재고, 실험실 타깃 패널의 흩어짐과 견준다. 마지막으로 글자를 두 배로 키웠다고 치고, 휴대폰 종이 위에 상자들을 다시 그려 어디가 먼저 넘치는지 표시한다.
실험실의 값은 진짜 기기에서 다시 본다 지후는 실험실의 결과를 그대로 코드에 옮기지 않았다. 실험실에서 고른 값으로 시험판을 고친 뒤, 확인은 늘 진짜 기기로 했다. 한도경의 휴대폰(글자 최대), 서윤의 휴대폰(DPR 3), 회사의 오래된 모니터(DPR 1), 그리고 브라우저 개발자 도구의 느린 통신 흉내. 목업은 몇 픽셀씩 틀릴 수 있지만, 어디서 깨지는지의 순서는 대개 맞는다. 그래서 실험실은 "어디를 진짜 기기로 먼저 볼지"를 정하는 데 가장 쓸모가 있었다고 지후는 말했다.

서윤의 작업 노트

CASE 서윤웹 개편 노트 · 13장

공개 전 마지막 점검, 세 페이지를 한 작업대에

2028년 2월 마지막 주 목요일 저녁, 책방 E 2층. 공개(3월 첫 주)까지 열흘이 남았다. 서윤은 노트북과 휴대폰을 나란히 놓고 첫 화면, 행사 일정, 모임 신청 폼을 실험실에 차례로 넣었다. 지후는 맞은편에서 시험판 코드를 열었고, 한도경은 글자를 가장 크게 한 휴대폰을 들고 왔다. 1월부터 장마다 따로 정한 값이 한 화면에서도 서로 어긋나지 않는지, 정하지 못하고 남겨 둔 것은 무엇인지 확인하는 자리였다.

먼저 장마다 처음 시도했던 값을 모은 "첫 시안 모음"을 띄워 출발점을 적었다. 폭 360에서 960 판이 600 px 넘쳤고, 글자 대비는 바닥글 2.62·날짜 줄 2.85, 포커스 표시는 없었고, 가장 작은 타깃은 머리 띠의 메뉴 링크 24 px(행사 일정의 날짜 칩은 28 px), 첫 화면 무게는 6.4 MB, 느린 모바일 LCP는 30초가 넘었다(모형). 그다음 "서윤의 선택"을 띄워 같은 줄을 다시 쟀다. 아래 표의 값은 이 장의 실험실로 잰 것이다(목업의 어림이라 진짜 브라우저와 몇 픽셀 다를 수 있다).

점검휴대폰 360 · 글자 100%휴대폰 360 · 글자 200%넓은 화면 1280기준
짜임 · 그리드1단 · 4단 거터 161단 · 4단 거터 32행 + 옆 칸 · 12단 · 단 78최소 폭 320에서 넘침 없음
본문 · 한 줄17 px · 약 25자34 px · 약 12자18 px · 약 37자상한 28 em(약 37자)
제목(h1)30 px60 px40 px5장 clamp() 식
가장 작은 타깃44(로고)52 · 칩 7544(로고)2.5.8 24 · 사이트 44
탭 바 · 메뉴4칸 · 칸 90 · 높이 584칸 · 글자 두 줄 · 높이 104머리 띠 5개8장
글자 대비 최저4.804.804.804.5 · 큰 글자 3
포커스 링 최저10.7410.7410.743 이상
첫 화면 무게(DPR 3)492 KB492 KB492 KB(DPR 2)500 KB
LCP 느린 모바일(모형)약 2.3초약 1.3초(글자가 LCP)약 2.3초2.5초
첫 화면 단서신청하기 · 일정 첫머리 보임신청하기가 첫 화면 밖모두 보임2장

표에서 빨간 줄은 없었지만, 실험실을 돌리는 동안 부딪침이 다섯 곳에서 나왔다(그림 3). 가장 먼저 걸린 것은 포커스 링이었다. 7장의 버튼 표와 11장의 점검표에는 #22342a가 적혀 있었는데, 시험판 코드에는 개발 템플릿의 #2f7d5b가 남아 있었다. 지후가 "코드에는 아직 템플릿 값이 들어가 있어요" 하고 Tab을 바닥글까지 누르자 "오시는 길"의 초록 링이 짙은 바닥글에 묻혔다. 실험실로 재니 2.64였다. 서윤은 7장의 값으로 맞췄다. 밝은 바탕에서는 짙은 링(최저 10.74, 크림 띠 위), 어두운 영역과 다크 모드에서는 크림 링(최저 10.74, 바닥글 위)이다. 코드와 두 장의 표에는 hex 대신 var(--focus-ring)을 적었다. 보조 글자도 6·11장의 #567666과 폼 코드의 #5d6b63이 갈라져 있어 토큰 하나로 합쳤다.

두 번째는 사진이었다. 서윤의 휴대폰(DPR 3)으로 시험판을 열자 개발자 도구에 1600 폭 사진이 찍혔다. 12장의 "800화소"는 DPR 2 휴대폰의 계산이었다. 실험실로 재니 첫 화면이 570 KB, 느린 모바일 LCP가 3.0초였다. 지후가 1200 폭을 하나 더 뽑아 srcset을 네 폭으로 만들자 492 KB, 2.3초가 되었다. 세 번째는 도경의 휴대폰이었다. 글자 200%, 폭 360에서 날짜 칩은 75 px로 커져 한 화면에 세 개 남짓만 보였다. 도경은 칩을 손가락으로 밀어 보더니 "옆으로 밀리는 걸 알겠네. 끝이 잘려 있으니까" 했다. 서윤은 칩을 줄이지 않았다. 표에 남은 마지막 주황 줄은 "첫 화면 단서"였다. 폭 360에서 사진을 16:9로 두자 행사 일정 제목이 첫 화면 끝(탭 바 위)보다 13 px 아래로 밀려 2장의 약속("행사 일정의 첫머리가 첫 화면 끝에 걸쳐 보이게")을 지키지 못했다. 서윤은 휴대폰에서만 사진을 5:2로 잘라 보이게 했다(같은 파일, aspect-ratio와 object-fit: cover). 제목이 첫 화면 안으로 올라왔고, 사진은 여전히 첫 화면에서 가장 넓은 요소라 LCP 후보로 남았다. 글자를 200%로 키운 도경의 화면에서는 "신청하기"도 첫 화면 밖으로 밀렸는데, 이것은 받아들였다. 큰 글자로 한 번 스크롤하는 것이 작은 글자로 한눈에 보는 것보다 낫다. 마지막으로 도경이 노트북 브라우저를 200%로 키우자 넓은 화면인데도 아래 탭 바가 붙었다. 지후는 고칠지 물었고, 서윤은 그대로 두자고 했다. 그 화면의 본문은 한 줄 22자 안팎으로, 넓은 짜임을 억지로 지키는 것보다 읽기 편했다.

도경은 마지막으로 서윤의 노트에 메모를 끼워 두었다. "표가 다 초록이어도 내 휴대폰으로 한 번은 더 해 봐라. 그리고 고르지 않은 것도 같이 적어 둬. 봄에 다시 고칠 때 그게 제일 쓸모 있을 거다." 지후의 메모는 짧았다. "값은 한 곳에. 나머지는 이름으로."

이 장에서 정한 것서윤의 선택(실험실 종합 점검 뒤). ① 앞 장의 값은 그대로 이어 쓴다. 최소 폭 320(2장), 간격 4·8·12·16·24·32·48·64와 안 < 밖 규칙(3장), 12단·거터 24·최대 1,200·카드 최소 240(4장), 중단점 35 em·56 em과 28 em 글줄 상한(5장), 의미 토큰과 따로 지정한 다크 모드(6장), 상태 다섯·최소 44(7장), 탭 바 넷(8장), 폼 네 칸(9장), 150/250 ms·곡선 하나(10장), 점검표 여섯 줄(11장), 500 KB 예산(12장). ② 포커스 색은 7장의 #22342a(3 px, 2 px 띄움, 흰 바탕 13.19)로 확정하고, 어두운 영역과 다크 모드에서는 #ffe2d7(바닥글 10.74, 다크 바탕 14.46). 값은 --focus-ring 토큰 한 곳에만 둔다. ③ 보조 글자는 text-muted #567666 하나로 합친다. ④ 좁은 화면 그리드 안내선: 35 em 아래 4단·거터 16, 35~56 em 8단·거터 24, 그 위 12단. ⑤ 첫 사진 srcset에 1200 폭(약 142 KB)을 더해 네 폭(480·800·1200·1600)으로, DPR 3 휴대폰에서도 첫 화면 492 KB·LCP 약 2.3초(모형). ⑥ 글자 200% 넓은 화면의 1단·탭 바 짜임은 고치지 않는다. ⑦ 휴대폰(35 em 아래)에서 첫 사진은 5:2로 잘라 보여 "신청하기"와 행사 일정 첫머리가 첫 화면(360×640, 탭 바 제외) 안에 든다. 탭 바는 HTML에서 머리 띠 바로 뒤에 두어 Tab 순서가 로고 → 탭 바 → 본문 → 바닥글이 되게 한다.
남겨 둔 다른 선택지이중 포커스 링(사진 위 버튼이 생기면, 7장 지후의 메모), sizes를 작게 적어 DPR 3에서도 800을 받게 하기(가볍지만 흐림), 첫 사진 예산을 늘리고 웹폰트를 한 굵기로 줄이기, 칩을 36 px·간격 12로 바꿔 한 줄에 일주일을 넣기(모형의 잘못 누르기는 1.4%로 비슷하지만 사이트 최소 44에서 벗어나고 빈 곳을 누르는 일이 는다), 글자 200% 넓은 화면에서도 머리 띠 메뉴를 유지하기. 두 설정의 점검표를 함께 노트에 남겼다.
다음 단계3월 첫 주 공개. 공개 전날 11장의 세 가지(키보드만으로 신청, 낭독기 단골손님의 제목·링크 듣기, 200% 확대)를 진짜 기기로 한 번 더 한다. 공개 뒤 한 달 동안 전화로 들어온 신청 수를 주인과 함께 센다. 겨울 웹 개편 노트 전체는 14장의 웹 개편 노트 색인에 모은다.

핵심 정리

  1. 장마다 따로 정한 값은 한 페이지 위에서 함께 점검한다. 폭, 확대, 글자 크기 설정 하나만 바꿔도 짜임, 글줄, 타깃, 무게가 같이 움직인다.
  2. 브라우저 확대는 CSS 뷰포트를 좁히고, 글자 크기 설정은 rem을 키운다. em 중단점은 둘 모두를 따라가서 글줄 기준(28 em, 약 37자)을 지킨다.
  3. 간격과 그리드는 값이 단계에 있는지보다 무리 안 < 카드 안 < 카드 사이 < 구획 사이의 순서와, 제목이 자기 목록에 붙는지로 본다.
  4. 대비는 페이지에 실제로 쓰인 글자–바탕 짝을 모두 잰다. 의미 토큰 하나를 바꾸면 그 토큰을 부르는 짝이 함께 바뀐다. 다크 모드는 반전이 아니라 따로 지정한다.
  5. 포커스 링은 그 링이 닿는 모든 바탕에서 3:1 이상이어야 한다. 이 책의 결정은 밝은 곳 #22342a, 어두운 곳 #ffe2d7이고, 값은 토큰 한 곳에 둔다.
  6. 터치 타깃은 고정 크기가 아니라 최소 크기(min-height: 44px)로 적어야 글자를 키워도 지켜진다. 2.5.8의 24는 하한이다.
  7. srcset은 CSS 폭 × 기기 픽셀 비율로 파일을 고른다. 무게 예산은 가장 높은 DPR의 휴대폰에서 다시 재 본다.

확인 퀴즈

1. 노트북 화면(폭 1,280 px)에서 브라우저를 400%로 확대했다. 이 페이지의 짜임을 정하는 CSS 뷰포트 폭은 얼마이고, 어떤 기준과 이어지는가?

브라우저 확대는 CSS 픽셀 하나를 더 크게 그리므로 같은 화면에 들어가는 CSS 픽셀의 수가 줄어든다. 1,280 ÷ 4 = 320이다. 1.4.10은 폭 320 CSS px에서 내용이 잘리거나 옆으로 밀 필요 없이 보이기를 요구하고, 2장에서 정한 최소 폭 320이 바로 이 값이다.

2. 7장과 11장은 포커스 링을 #22342a로 적었는데, 시험판 코드에는 #2f7d5b가 들어가 있었다. 실험실에서 #22342a(어두운 곳은 크림 #ffe2d7)를 고른 근거로 가장 알맞은 것은?

포커스 링은 비텍스트 요소라 닿는 바탕과 3:1 이상이어야 한다. 초록 링은 밝은 바탕에서는 통과하지만 짙은 바닥글 위(2.64)에서 실패한다. 짙은 링은 밝은 바탕에서 13.19·12.68·10.74, 크림 링은 어두운 바탕에서 10.74·14.46이다. 취향이나 어느 쪽에 적혔는지가 아니라 모든 바탕에서의 대비가 근거다.

3. 첫 사진이 화면에서 CSS 폭 328 px을 차지하고, srcset에 480·800·1600 세 폭이 있다. 기기 픽셀 비율 3의 휴대폰은 어느 파일을 받는가?

브라우저는 sizes로 정한 CSS 폭에 기기 픽셀 비율을 곱한 화소 수를 채우는 가장 작은 후보를 고른다(캐시나 데이터 절약 설정에 따라 달라질 수 있다). 984화소를 채우려면 1600이 필요하다. 그래서 이 장은 1200 폭을 더해 DPR 3에서도 무게 예산 안에 들게 했다.

4. "신청하기" 버튼을 width: 120px; height: 36px로 못 박았다. 글자 크기 설정을 200%로 키운 휴대폰에서 일어날 일과 고칠 방법으로 가장 알맞은 것은?

rem으로 적은 글자는 사용자 설정을 따라 커지지만, px로 못 박은 상자는 그대로다. 7장에서 도경의 휴대폰에 "모임 신청하"까지만 보인 이유다. 크기를 최소값으로 적으면 기본 상태에서는 44를 지키고, 글자가 커지면 상자도 따라 커진다. 글자를 px로 막는 것은 1.4.4(글자 크기 조절)를 어기는 방향이다.

참고 문헌

W3C, Web Content Accessibility Guidelines (WCAG) 2.2, 2023(성공 기준 1.4.3 대비 최소, 1.4.4 글자 크기 조절, 1.4.10 리플로, 1.4.11 비텍스트 대비, 2.4.7 포커스 보임, 2.4.13 포커스 모양, 2.5.8 타깃 크기 최소)과 Understanding WCAG 2.2 · 한국지능정보사회진흥원, 한국형 웹 콘텐츠 접근성 지침(KWCAG) 2.2 · web.dev, Core Web Vitals 기준값(LCP 2.5초, INP 200 ms, CLS 0.1) · WHATWG HTML Living Standard, img의 srcset·sizes 선택. 대비율은 이 서가의 계산 엔진(ART.contrast)으로, 무게와 LCP는 12장의 교육용 모형으로, 터치 흩어짐은 7장의 교육용 모형으로 계산했다. 이 장의 페이지, 행사, 책 제목은 모두 가상이다.