반응형·모바일·접근성
화면 크기와 입력 방식이 달라도, 보조 기술을 써도 같은 일을 할 수 있게 만드는 법이다.
목차 (13)
모바일 퍼스트와 콘텐츠 기준 브레이크포인트
반응형 디자인은 화면마다 다른 사이트를 만드는 대신 하나의 페이지가 폭에 따라 다시 흐르게 하는 것이다. 순서는 모바일 퍼스트가 기본이다. 좁은 화면의 스타일을 기본 규칙으로 먼저 쓰고, min-width 미디어 쿼리로 넓은 화면에서만 필요한 규칙을 더한다. 좁은 화면에서 꼭 필요한 것부터 정하게 되어 우선순위가 분명해지고, 작은 기기가 쓰지 않을 규칙을 덮어쓸 일도 줄어든다.
브레이크포인트를 어디에 둘지는 기기 목록이 아니라 내용이 정한다. web.dev는 인기 있는 기기 크기가 아니라 콘텐츠를 기준으로 브레이크포인트를 고르라고 하고, 글이 중심인 페이지는 글자 크기에 따르는 em·ch 같은 상대 단위가 더 맞을 수 있다고 한다. 창을 천천히 넓혀 보다가 한 줄이 너무 길어지거나 카드가 어색하게 늘어나는 폭이 브레이크포인트다.
360px: 한 열. 본문 폭 328px에 최소 240px 카드가 1열(한 장 328px)로 들어간다.
/* 기본: 좁은 화면. 모든 화면이 이 규칙을 받는다 */
.cards { display: grid; gap: 1rem; }
/* 카드가 두 장 들어갈 만큼 넓어지면 */
@media (min-width: 37.5em) {
.cards { grid-template-columns: repeat(auto-fill, minmax(15rem, 1fr)); }
}
/* 옆 메뉴를 둘 만큼 넓어지면 */
@media (min-width: 56.25em) {
.page { display: grid; grid-template-columns: 13.75rem 1fr; }
}브레이크포인트 사이에서는 유동 레이아웃이 일한다. 폭을 고정 픽셀로 정하지 않고 비율(%), fr, minmax()로 정하면 브레이크포인트가 아닌 폭에서도 자연스럽게 늘고 준다. 위 예의 repeat(auto-fill, minmax(15rem, 1fr))는 카드가 240px보다 좁아지지 않는 선에서 들어갈 만큼의 열을 만든다. 렌더링·성능 쪽 이야기는 웹 개발 노트에 있다.
컨테이너 쿼리: 화면이 아니라 칸에 반응하기
미디어 쿼리는 화면 폭을 본다. 그런데 같은 카드가 넓은 본문에도, 좁은 옆 칸에도 들어간다면 화면 폭으로는 카드의 모양을 정할 수 없다. 컨테이너 쿼리는 카드를 담은 칸의 폭을 기준으로 스타일을 바꾼다. MDN의 방법대로 담는 요소에 container-type: inline-size를 주고, 안쪽 규칙은 @container로 쓴다. 칸 폭의 1%를 뜻하는 cqi·cqw 같은 단위도 있다.
.card-slot { container-type: inline-size; }
.card { display: grid; gap: 0.5rem; }
@container (width > 260px) {
.card { grid-template-columns: 7rem 1fr; }
}페이지의 큰 틀(옆 메뉴를 둘지, 몇 단으로 할지)은 미디어 쿼리로, 그 안에 들어가는 컴포넌트의 모양은 컨테이너 쿼리로 나누면 컴포넌트를 어디에 옮겨도 다시 손볼 일이 적다. 디자인 시스템의 컴포넌트를 만들 때 특히 쓸모 있다(디자인 시스템 노트).
상대 단위와 유동 타이포: rem, %, vw, clamp()
단위는 무엇에 따라 커져야 하는지로 고른다. 글자 크기와 그에 딸린 여백은 rem(뿌리 요소의 글자 크기, 브라우저 기본 16px)으로 쓴다. 그래야 사용자가 브라우저 설정에서 글자를 키울 때 함께 커진다. 칸 폭은 %나 fr로, 화면 폭에 따라야 하는 것은 vw(화면 폭의 1%)로 쓴다. 글줄 길이는 max-width를 rem이나 ch로 막아 넓은 화면에서 한 줄이 끝없이 길어지지 않게 한다.
제목처럼 화면에 따라 부드럽게 커져야 하는 글자는 clamp(최소, 선호, 최대)로 쓴다. MDN에 따르면 이것은 max(최소, min(선호, 최대))와 같다. 선호값에 rem + vw를 섞으면 두 폭 사이에서 직선으로 커진다. 360px에서 1.5rem, 1280px에서 3rem이 되게 하는 식은 clamp(1.5rem, 0.913rem + 2.609vw, 3rem)다. 기울기는 (48 − 24) / (1280 − 360)이고, vw 계수는 그 100배다.
h1 {
/* 360px에서 1.5rem, 1280px에서 3rem, 그 사이는 직선 */
font-size: clamp(1.5rem, 0.913rem + 2.609vw, 3rem);
}
.card { padding: 1rem; max-width: 40rem; }
img { max-width: 100%; height: auto; }MDN은 글자 크기에 clamp()를 쓸 때 최대값을 최소값의 두 배 이상인 상대 단위로 두라고 권한다. 그래야 페이지를 확대했을 때 글자가 적어도 200%까지 커질 수 있다. 위 식은 1.5rem과 3rem이라 이 조건을 맞춘다. 글자 크기를 vw만으로 정하는 것은 피한다.
반응형 이미지: srcset, sizes, picture
휴대폰에 데스크톱용 큰 사진을 보내면 데이터와 시간을 버리고, 고해상도 노트북에 작은 사진을 보내면 흐려진다. MDN의 방법은 둘이다. 같은 사진을 크기만 달리 보낼 때는 srcset에 파일마다 실제 폭(480w)을 적고, sizes에 화면 조건마다 이미지가 차지할 슬롯 폭을 적는다. 브라우저는 맞는 조건의 슬롯 폭과 화면 밀도를 보고 맞는 파일을 고르고, 딱 맞는 것이 없으면 슬롯보다 큰 것 가운데 첫 번째를 골라 줄인다.
<img
src="hero-800.jpg"
srcset="hero-480.jpg 480w, hero-800.jpg 800w,
hero-1200.jpg 1200w, hero-1600.jpg 1600w"
sizes="(width <= 600px) 100vw, 800px"
alt="해 질 녘 한강 다리 위 자전거 행렬">- 구도가 달라야 하면 picture: 좁은 화면에서 인물을 가까이 자른 다른 사진을 보여 주려면
<picture>안의<source media>를 쓴다. 맞는 첫source를 쓰고, 안의<img>는 기본값이자 대비책이다. - 크기 고정이면 x 서술자: 아이콘처럼 표시 크기가 늘 같으면
1.5x·2x만 적으면 되고sizes는 필요 없다. - 자리를 미리 잡는다:
width·height속성으로 비율을 알려 주면 사진이 늦게 와도 아래 내용이 밀리지 않는다. 화면 밀리기(CLS)는 웹 개발 노트에 있다.
터치 타깃과 엄지 영역
손가락은 마우스 커서보다 크고 덜 정확하다. 그래서 플랫폼마다 최소 터치 크기가 있다. iOS 44pt, 안드로이드 48dp, WCAG 2.5.8(AA)의 24×24 CSS px와 그 근거는 HCI와 UX 노트의 피츠의 법칙·접근성 절에 있다. WCAG에는 더 엄격한 2.5.5(AAA)도 있어, 예외가 아니면 타깃을 44×44 CSS px 이상으로 하라고 한다.
어디를 누르기 쉬운지는 사람이 휴대폰을 어떻게 쥐는지에 달려 있다. 스티븐 후버(Steven Hoober)가 2013년에 화면을 누르던 사람 780명을 관찰한 연구에서는 한 손이 49%, 다른 손으로 감싸 쥐는 경우가 36%, 두 손이 15%였다. 이 연구의 엄지 도달 그림이 "엄지 영역"으로 널리 퍼졌지만, 후버 자신이 2017년에 그 그림이 부정확하다고 밝히고 후속 연구를 보라고 덧붙였다.
후속 연구인 Design for Fingers, Touch, and People의 결론은 이렇다. 사람은 화면 가운데를 가장 많이 보고 누르며, 읽을 것을 가운데로 스크롤해 온다. 가운데는 정확도가 높아 약 7mm 타깃이면 되지만 모서리는 약 12mm가 필요하다. 쥐는 방식은 쓰는 도중에도 모르는 새 바뀐다. 그래서 주 콘텐츠는 가운데에, 탭·동작 버튼 같은 보조 기능은 위아래 가장자리에, 드물게 쓰는 기능은 모서리의 메뉴에 둔다. 잘못 눌렀을 때의 결과가 작도록 위험한 버튼은 자주 누르는 버튼과 떨어뜨린다.
세이프 영역과 가로·세로 모드
요즘 휴대폰은 모서리가 둥글고 카메라 홈과 홈 표시줄이 화면 일부를 차지한다. 웹 페이지를 화면 끝까지 그리려면 viewport 메타 태그에 viewport-fit=cover를 주는데, MDN은 이때 중요한 내용이 화면 밖으로 나가지 않도록 세이프 영역 값을 쓰라고 강하게 권한다. 그 값이 env(safe-area-inset-*)다. 화면이 직사각형이고 가리는 것이 없으면 0, 아니면 가린 만큼의 px 값이 된다.
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
.bottom-bar {
position: sticky;
bottom: 0;
padding: 0.75rem 1rem calc(0.75rem + env(safe-area-inset-bottom));
}- 방향을 잠그지 않는다: WCAG 1.3.4(방향, AA)는 꼭 필요한 경우가 아니면 화면을 세로나 가로 한 방향으로 묶지 말라고 한다. 휠체어에 기기를 고정해 쓰는 사람은 돌릴 수 없다. 피아노 앱이나 은행 수표 촬영처럼 방향이 본질인 경우만 예외다.
- 확대를 막지 않는다:
user-scalable=no나 작은maximum-scale은 저시력 사용자가 확대해 읽는 것을 막는다. MDN은 WCAG가 최소 2배 확대를 요구하고 5배까지 허용하는 것이 좋다고 적는다. - 가로 모드의 키보드: 가로로 돌린 휴대폰에서 가상 키보드가 뜨면 남는 높이가 아주 작다. 고정 머리글과 바닥 막대가 그 공간을 다 차지하지 않게 한다.
모바일 앱의 pt·dp 단위와 화면 밀도는 모바일 개발 노트에, 게임 화면의 세이프 영역은 게임 UI 노트에 있다.
접근성의 틀: WCAG의 네 원칙과 2.2
웹 접근성의 기준은 W3C의 WCAG(Web Content Accessibility Guidelines)다. 모든 성공 기준은 네 원칙 아래에 있고, 머리글자를 따 POUR라 부른다. MDN의 정리대로 인식할 수 있어야 하고(하나 이상의 감각으로 알아챌 수 있게), 운용할 수 있어야 하고(마우스·키보드·음성 등 어떤 방법으로든 조작할 수 있게), 이해할 수 있어야 하고, 견고해야 한다(여러 브라우저와 보조 기술에서 지금도 앞으로도 동작하게 표준을 따라).
현재 권고안인 WCAG 2.2는 2023년 10월 5일에 나왔다. W3C의 정리에 따르면 성공 기준 9개가 새로 들어왔고, 더 이상 쓸모없어진 4.1.1(구문 분석)이 빠졌다. 나머지 2.0·2.1의 기준은 거의 그대로다.
| 번호 | 이름 | 수준 | 요지 |
|---|---|---|---|
| 2.4.11 | 포커스 가리지 않음(최소) | AA | 포커스 받은 요소가 고정 머리글 같은 것에 완전히 가려지지 않는다 |
| 2.4.12 | 포커스 가리지 않음(강화) | AAA | 조금도 가려지지 않는다 |
| 2.4.13 | 포커스 표시 모양 | AAA | 포커스 표시가 최소 크기와 대비를 갖는다 |
| 2.5.7 | 끌기 동작 | AA | 끌어서 하는 일을 누르기만으로도 할 수 있다 |
| 2.5.8 | 타깃 크기(최소) | AA | 24×24 CSS px 이상, 또는 그만큼 띄운다 |
| 3.2.6 | 일관된 도움말 | A | 여러 페이지의 도움말 수단이 같은 상대 위치에 있다 |
| 3.3.7 | 중복 입력 | A | 한 과정에서 이미 입력한 정보를 다시 묻지 않고 채우거나 고르게 한다 |
| 3.3.8 | 접근 가능한 인증(최소) | AA | 외우거나 옮겨 적는 인지 시험을 로그인에 강요하지 않는다 |
| 3.3.9 | 접근 가능한 인증(강화) | AAA | 사물 알아보기 같은 시험도 강요하지 않는다 |
3.3.8은 비밀번호 붙여넣기와 비밀번호 관리자를 허용하라는 폼과 입력 노트의 권고와 이어지고, 2.5.7은 인터랙션과 상태 노트의 제스처 대체 수단과 같은 이야기다.
시맨틱 HTML과 ARIA의 첫 규칙
화면 낭독기 같은 보조 기술은 화면의 픽셀이 아니라 HTML이 알려 주는 역할·이름·상태를 읽는다. 그래서 접근성의 대부분은 맞는 요소를 쓰는 데서 나온다. 제목은 h1~h6, 목록은 ul, 내비게이션은 nav, 누르는 것은 button, 이동하는 것은 a다. WCAG 1.3.1(정보와 관계, A)은 모양으로 전하는 정보·구조·관계를 프로그램이 알아낼 수 있어야 한다고 요구한다. 굵은 글자로 꾸민 문단은 눈에는 제목이지만 낭독기에는 그냥 문단이다.
ARIA(Accessible Rich Internet Applications)는 HTML만으로 표현할 수 없는 역할과 상태(탭, 펼침 여부, 실시간 알림 영역)를 더하는 속성이다. W3C의 Using ARIA가 정한 첫 규칙은 필요한 의미와 동작을 이미 가진 HTML 요소나 속성이 있으면 그것을 쓰라는 것이고, 둘째 규칙은 꼭 필요한 경우가 아니면 요소가 원래 가진 의미를 바꾸지 말라는 것이다. ARIA는 의미만 바꿀 뿐 동작을 더하지 않으므로, role="button"을 달았다면 키보드 처리도 직접 해야 한다. 웹 쪽 기본기는 웹 개발 노트의 접근성 절에도 있다.
키보드 내비게이션, 포커스 순서, 스크린 리더의 읽기 순서
마우스 없이 키보드만으로 모든 기능을 쓸 수 있어야 한다. Tab으로 다음, Shift+Tab으로 이전, Enter·Space로 실행, 화살표로 메뉴·탭 안을 움직인다. 포커스가 지나는 순서는 의미와 조작을 지키는 순서여야 하고(2.4.3 포커스 순서, A), 포커스가 어디 있는지 보여야 한다(2.4.7 포커스 보임, AA). 포커스 링을 지우면 안 되는 이유는 인터랙션과 상태 노트에 있다.
순서가 꼬이는 흔한 원인은 CSS다. Flexbox·Grid의 order나 절대 위치로 화면 배치만 바꾸면 눈으로 보는 순서는 바뀌어도 Tab과 화면 낭독기는 여전히 DOM(HTML에 적힌) 순서를 따른다. W3C는 1.3.2(의미 있는 순서, A)의 충분한 방법으로 DOM 순서를 보이는 순서와 맞추는 것을 들고, CSS로 위치를 바꿔 내용의 뜻이 달라지는 것을 실패 사례로 든다(W3C). tabindex에 양수를 줘 순서를 억지로 정하는 것도 같은 문제를 부르기 쉽다. W3C는 tabindex로 뜻과 조작을 해치는 순서를 만드는 것을 실패 사례(F44)로 든다(W3C).
CSS order로 위치만 옮김: Tab 1번째 → "장바구니". 눈으로는 상품 이름이 먼저인데 포커스와 낭독은 장바구니부터 간다(어긋난 자리 3곳).
화면 낭독기는 이 DOM 순서로 한 항목씩 읽는다. 안드로이드의 TalkBack은 오른쪽으로 밀면 다음 항목, 왼쪽으로 밀면 이전 항목으로 가고, 두 번 탭하면 선택한 항목을 실행한다(TalkBack 도움말). iOS의 VoiceOver도 손가락을 밀어 항목을 옮기고 두 번 탭해 실행하는 방식이다. 직접 켜서 내 화면을 한 번 끝까지 들어 보는 것이 가장 확실한 검사다. 제목만 골라 듣거나 링크만 골라 듣는 기능도 있어, 제목 구조가 엉망이면 페이지를 훑을 길이 사라진다.
- 건너뛰기 링크: 페이지 맨 앞에 "본문으로 건너뛰기"를 두면 키보드 사용자가 매번 메뉴를 다 지나지 않아도 된다.
- 모달은 포커스를 가둔다: 대화상자가 열리면 포커스를 그 안으로 옮기고, 닫으면 연 버튼으로 돌려준다. 열린 동안 Tab이 뒤쪽 페이지로 새어 나가지 않게 한다.
- 바뀐 내용은 알린다: 화면 일부만 바뀌는 알림(저장됨, 오류 수)은
aria-live영역에 넣어야 낭독기가 읽는다.
대체 텍스트 쓰는 법
그림을 볼 수 없는 사람에게는 alt가 그 그림이다. 좋은 대체 텍스트는 그림을 묘사하는 것이 아니라 그 자리에서 그림이 하는 일을 대신한다. W3C의 alt 결정 나무는 다섯 질문으로 정리한다.
- 꾸밈: 구분선·배경 무늬처럼 정보가 없는 그림은
alt=""(빈 값)로 낭독기가 건너뛰게 한다. - 기능: 링크나 버튼 안의 그림은 그림의 모양이 아니라 누르면 어디로 가는지, 무엇을 하는지를 쓴다.
- 글자 그림: 그림에 든 글자가 다른 곳에 없으면 그 글자를 그대로 쓴다.
- 정보: 사진·아이콘이 뜻을 더하면 그 뜻을 짧게 쓴다. 앞뒤 글이 이미 말한 것은 되풀이하지 않는다.
- 복잡한 그림: 그래프·도표의 자세한 내용은 alt에 넣지 말고 페이지의 본문이나 표로 따로 둔다. 이 노트의 그림들도 SVG의 제목에 요약을 달고, 자세한 내용은 캡션과 본문에 둔다.
색만으로 전하지 않기, 확대 200%와 리플로
WCAG 1.4.1(색 사용, A)은 색을 정보 전달, 동작 지시, 응답 요구, 요소 구분의 유일한 시각적 수단으로 쓰지 말라고 한다. W3C는 필수 칸이나 오류 칸을 색 차이로만 구분하는 것, 색을 빼면 본문과 구분되지 않는 링크를 실패 사례로 들고, 본문 속 링크는 주변 글과 3:1 이상 대비를 두는 방법을 소개한다(W3C). 실제로 어떻게 보이는지는 이 사이트의 색각 이상 시뮬레이터로 확인할 수 있다.
글자를 키우는 사람도 많다. WCAG 1.4.4(글자 크기 조절, AA)는 자막과 글자 그림을 빼고 보조 기술 없이 글자를 200%까지 키워도 내용과 기능이 사라지지 않아야 한다고 하고, 1.4.10(리플로, AA)은 세로로 읽는 내용을 폭 320 CSS px에서 가로·세로 두 방향 스크롤 없이 볼 수 있어야 한다고 한다. 320은 1280px 화면을 400%로 확대한 폭이다(1280 ÷ 4 = 320). 지도나 큰 표처럼 두 방향 배치가 본질인 내용은 예외다(W3C).
확대와 반응형은 같은 문제다. 브레이크포인트를 em으로, 글자를 rem으로 쓰고, 높이를 고정하지 않고, 가로 폭을 고정 픽셀로 묶지 않으면 따로 손대지 않아도 대부분 통과한다. 반대로 height: 40px에 글자를 담은 버튼은 글자를 키우면 잘린다.
흔한 오해
- "브레이크포인트는 아이폰·아이패드 폭에 맞춘다" — 기기는 해마다 바뀐다. 내용이 어색해지는 폭에 두고 em으로 쓴다.
- "반응형이면 접근성도 된다" — 좁은 화면에 맞는 것과 키보드·낭독기로 쓸 수 있는 것은 다르다. 둘 다 따로 시험한다.
- "엄지 영역은 화면 아래쪽이다" — 널리 퍼진 그림을 만든 연구자 스스로 부정확하다고 했다. 사람은 가운데를 가장 많이 보고 누른다.
- "ARIA를 많이 달수록 접근성이 좋다" — 첫 규칙은 맞는 HTML 요소를 쓰는 것이다. 동작 없는 role은 고장 난 버튼을 만든다.
- "모든 그림에 자세한 alt를 단다" — 꾸밈 그림은 빈 alt가 맞고, 복잡한 그림은 본문에 설명을 둔다. 그림이 그 자리에서 하는 일을 쓴다.
- "확대를 막아야 레이아웃이 안 깨진다" — 확대를 막으면 저시력 사용자가 읽지 못한다. 확대해도 다시 흐르게 만드는 것이 해법이다.
- "자동 검사 도구가 통과하면 된다" — 도구는 대비·빠진 alt 같은 일부만 잡는다. 키보드만으로 한 번, 화면 낭독기로 한 번 직접 써 본다.
핵심 자료
요지는 직접 정리한 한 줄이다.
| 자료 | 한 줄 요지 |
|---|---|
| web.dev — Learn Responsive Design | 브레이크포인트는 기기 크기가 아니라 콘텐츠가 깨지는 지점에서 정한다. |
| MDN — Container queries | 컴포넌트가 화면이 아니라 담긴 칸의 폭에 반응하게 하는 법. |
| MDN — Responsive images | srcset·sizes로 해상도를, picture로 구도를 바꾸는 법과 브라우저의 선택. |
| Steven Hoober — Design for Fingers, Touch, and People | 사람은 화면 가운데를 가장 많이 보고 누르며, 가운데 타깃은 더 작아도 된다. |
| W3C — What's New in WCAG 2.2 | 2.2에 더해진 성공 기준 9개와 빠진 4.1.1. |
| W3C — Using ARIA | ARIA 첫 규칙: 맞는 HTML 요소가 있으면 그것을 쓴다. |
| W3C — An alt Decision Tree | 그림의 쓰임에 따라 alt를 비우거나, 기능·글자·뜻을 적는 질문 다섯. |
- Accessibility — MDN Web Docs
- Media queries — web.dev Learn Responsive Design
- Container queries — MDN Web Docs
- clamp() — MDN Web Docs
- Responsive images — MDN Web Docs
- How Do Users Really Hold Mobile Devices? — UXmatters
- Design for Fingers, Touch, and People, Part 1 — UXmatters
- env() — MDN Web Docs
- <meta name="viewport"> — MDN Web Docs
- Understanding WCAG — MDN Web Docs
- What's New in WCAG 2.2 — W3C WAI
- Using ARIA — W3C
- Understanding SC 2.4.3 Focus Order — W3C
- Understanding SC 1.3.2 Meaningful Sequence — W3C
- An alt Decision Tree — W3C WAI
- Understanding SC 1.4.1 Use of Color — W3C
- Understanding SC 1.4.4 Resize Text — W3C
- Understanding SC 1.4.10 Reflow — W3C
- Understanding SC 1.3.4 Orientation — W3C
- Understanding SC 2.5.5 Target Size (Enhanced) — W3C
- Use TalkBack gestures — Android Accessibility Help