테마 선택

📚 지식 노트

AI·하드웨어·주변기기·게임·컴퓨터 시스템·이론 컴퓨터 과학·수학·개발과 운영·보안·응용 분야에 대해 알아 두면 좋은 내용을 정리한 노트다. 위에서 분야를 고르고 아래에서 노트를 고른다.

반응형·모바일·접근성

화면 크기와 입력 방식이 달라도, 보조 기술을 써도 같은 일을 할 수 있게 만드는 법이다.

목차 (13)
  1. 모바일 퍼스트와 콘텐츠 기준 브레이크포인트
  2. 컨테이너 쿼리: 화면이 아니라 칸에 반응하기
  3. 상대 단위와 유동 타이포: rem, %, vw, clamp()
  4. 반응형 이미지: srcset, sizes, picture
  5. 터치 타깃과 엄지 영역
  6. 세이프 영역과 가로·세로 모드
  7. 접근성의 틀: WCAG의 네 원칙과 2.2
  8. 시맨틱 HTML과 ARIA의 첫 규칙
  9. 키보드 내비게이션, 포커스 순서, 스크린 리더의 읽기 순서
  10. 대체 텍스트 쓰는 법
  11. 색만으로 전하지 않기, 확대 200%와 리플로
  12. 흔한 오해
  13. 핵심 자료

모바일 퍼스트와 콘텐츠 기준 브레이크포인트

반응형 디자인은 화면마다 다른 사이트를 만드는 대신 하나의 페이지가 폭에 따라 다시 흐르게 하는 것이다. 순서는 모바일 퍼스트가 기본이다. 좁은 화면의 스타일을 기본 규칙으로 먼저 쓰고, min-width 미디어 쿼리로 넓은 화면에서만 필요한 규칙을 더한다. 좁은 화면에서 꼭 필요한 것부터 정하게 되어 우선순위가 분명해지고, 작은 기기가 쓰지 않을 규칙을 덮어쓸 일도 줄어든다.

모바일 퍼스트 CSS의 세 층. 맨 아래 넓은 층이 모든 화면에 쓰는 기본 규칙이고, 그 위에 폭 37.5em 이상에서 카드 두 열, 56.25em 이상에서 옆 메뉴를 더하는 좁은 층이 차례로 쌓인다기본 규칙 (모든 화면)한 열, 큰 글자, 터치 크기@media (min-width: 37.5em)+ 카드 두 열@media (min-width: 56.25em)+ 옆 메뉴넓은 화면일수록 위층까지 적용된다37.5em = 600px, 56.25em = 900px(기본 16px). 글자를 키우면 함께 밀린다
모바일 퍼스트 CSS의 층. 넓은 화면일수록 위층의 규칙을 더 받는다. em 단위로 쓰면 사용자가 글자를 키울 때 브레이크포인트도 함께 움직인다.

브레이크포인트를 어디에 둘지는 기기 목록이 아니라 내용이 정한다. web.dev는 인기 있는 기기 크기가 아니라 콘텐츠를 기준으로 브레이크포인트를 고르라고 하고, 글이 중심인 페이지는 글자 크기에 따르는 em·ch 같은 상대 단위가 더 맞을 수 있다고 한다. 창을 천천히 넓혀 보다가 한 줄이 너무 길어지거나 카드가 어색하게 늘어나는 폭이 브레이크포인트다.

브레이크포인트 단계 그림: 360픽셀 화면은 카드가 한 열로 쌓이고, 600픽셀은 두 열, 900픽셀은 왼쪽 메뉴와 두 열, 1280픽셀은 왼쪽 메뉴와 네 열이 된다360px · 한 열 · 카드 1열600px · 카드 격자 · 카드 2열900px · 옆 메뉴 + 격자 · 카드 2열1280px · 옆 메뉴 + 격자 · 카드 4열축척 0.34 · 위 막대 = 머리글, 파란 상자 = 카드 4장, 옅은 칸 = 옆 메뉴
1 / 4

360px: 한 열. 본문 폭 328px에 최소 240px 카드가 1열(한 장 328px)로 들어간다.

화면 폭이 넓어질 때 레이아웃이 바뀌는 과정. 브레이크포인트(600px·900px)는 기기 목록이 아니라 카드가 너무 넓어지거나 좁아지는 지점에서 정했다. 열 수와 카드 폭은 계산 함수로 구했다.
/* 기본: 좁은 화면. 모든 화면이 이 규칙을 받는다 */
.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 같은 단위도 있다.

한 페이지 안에 같은 카드 컴포넌트가 두 번 있다. 폭 120의 옆 칸에서는 사진이 위, 글이 아래인 세로형이고, 폭 298의 본문에서는 사진이 왼쪽, 글이 오른쪽인 가로형이다같은 화면, 같은 카드 — 담은 칸의 폭이 다르다제주 3박 4일숙소 · 렌터카제주 3박 4일숙소 · 렌터카 · 일정표11월 13일 출발기본 규칙: 세로형@container (width > 260px): 가로형
한 화면 안의 같은 카드. 옆 칸에서는 세로형, 본문에서는 가로형이다. 카드는 어디에 놓일지 몰라도 스스로 모양을 고른다.
.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배다.

화면 폭에 따른 제목 글자 크기 그래프. clamp 식은 360픽셀까지 24픽셀, 1280픽셀부터 48픽셀로 고정되고 그 사이는 직선으로 커진다. 사용자가 기본 글자를 32픽셀로 키우면 곡선 전체가 위로 올라가지만, vw만 쓴 선은 글자 설정과 상관없이 같다font-size: clamp(1.5rem, 0.913rem + 2.609vw, 3rem)02040608032064096012801600화면 폭(px) · 세로는 글자 크기(px)clamp, 기본 16pxclamp, 글자 크기 32px3.75vw만 쓴 경우360px에서24px → 48px최대값이 최소값의 두 배 이상인 rem이라, 글자를 200%로 키우면 제목도 따라 커진다
같은 clamp() 제목을 기본 글자 16px과 사용자가 키운 32px에서, 그리고 vw만 쓴 경우와 비교했다. vw만 쓰면 사용자가 글자를 키워도 그대로다. 값은 계산 함수로 구했다.
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에 화면 조건마다 이미지가 차지할 슬롯 폭을 적는다. 브라우저는 맞는 조건의 슬롯 폭과 화면 밀도를 보고 맞는 파일을 고르고, 딱 맞는 것이 없으면 슬롯보다 큰 것 가운데 첫 번째를 골라 줄인다.

srcset 후보 네 개 가운데 고르는 경우 넷. 휴대폰 360 슬롯 1배는 480, 3배는 1200, 노트북 800 슬롯 1배는 800, 2배는 1600 폭의 파일을 고른다. 점선은 필요한 픽셀 폭이다srcset="hero-480.jpg 480w, …800w, …1200w, …1600w"경우후보 파일 폭(고른 것 = 채움)휴대폰 · 슬롯 360px× 1배 = 360px 필요480w휴대폰 · 슬롯 360px× 3배 = 1080px 필요1200w노트북 · 슬롯 800px× 1배 = 800px 필요800w노트북 · 슬롯 800px× 2배 = 1600px 필요1600w겹친 막대 = 후보 넷(480·800·1200·1600). 필요한 폭(점선) 이상인 가장 작은 후보를 받는다sizes가 슬롯 폭을 알려 준다. 실제 브라우저는 네트워크와 캐시도 본다
슬롯 폭 × 기기 픽셀 비율이 필요한 픽셀 수다. 같은 360px 슬롯이라도 3배 화면은 1200w 파일을 받는다. 선택은 단순화한 계산 함수로 했다.
<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 이상으로 하라고 한다.

왼쪽 휴대폰은 가운데에 주 콘텐츠, 위아래 가장자리에 탭과 동작 버튼, 모서리에 메뉴를 둔 배치와, 가운데 7밀리미터, 모서리 12밀리미터라는 필요한 타깃 크기를 보인다. 가운데 막대는 휴대폰을 한 손으로 49퍼센트, 감싸 쥐고 36퍼센트, 두 손으로 15퍼센트 쓴다는 관찰이고, 오른쪽은 기준별 최소 타깃 크기 비교다주 콘텐츠약 7mm면 맞힌다탭 · 동작 버튼≡모서리 약 12mmHoober(2017): 가운데를 가장 많이 보고 누른다쥐는 방식화면을 누르던 780명(Hoober 2013)한 손49%감싸 쥠36%두 손15%쥐는 법은 쓰는 도중에도모르는 새 바뀐다최소 타깃24 CSS pxWCAG 2.5.8 AA44 CSS pxWCAG 2.5.5 AAA44 ptiOS 기본48 dpAndroid 최소
왼쪽은 사람이 많이 보고 누르는 가운데에 주 콘텐츠를, 가장자리에 탭과 동작을, 모서리에 메뉴를 둔 배치다. 가운데 막대는 쥐는 방식의 비율, 오른쪽은 기준별 최소 타깃이다(단위가 서로 달라 크기 비교는 대략이다).

어디를 누르기 쉬운지는 사람이 휴대폰을 어떻게 쥐는지에 달려 있다. 스티븐 후버(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 값이 된다.

세로 휴대폰은 위쪽 카메라 홈 아래와 아래쪽 홈 표시줄 위로 내용 영역이 줄고, 가로 휴대폰은 카메라 홈이 있는 왼쪽과 반대쪽 오른쪽, 아래쪽이 안쪽으로 들어간다. 줄어든 만큼이 safe-area-inset 값이다세로안전한 영역inset-topinset-bottom가로로 돌리면안전한 영역inset-leftinset-right · inset-bottom배경은 끝까지, 글과 버튼은 안전한 영역 안에
세로에서는 위아래, 가로로 돌리면 왼쪽·오른쪽이 가려진다. 배경은 끝까지 칠하고 글과 버튼은 안전한 영역 안에 둔다.
<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의 네 원칙을 기둥 넷으로 그렸다. 인식은 대체 텍스트와 색에 기대지 않기, 운용은 키보드와 포커스, 이해는 레이블과 오류 설명, 견고는 시맨틱 HTML과 올바른 ARIA가 예다WCAG 2.2 · 성공 기준 A / AA / AAAP인식Perceivable대체 텍스트색만으로 X글자 확대O운용Operable키보드로 전부포커스 보임타깃 크기U이해Understandable분명한 레이블오류 설명예측 가능R견고Robust시맨틱 HTML올바른 ARIA표준 준수원칙 아래 지침, 지침 아래 검사할 수 있는 성공 기준이 있다
WCAG의 네 원칙과 이 노트의 예. 성공 기준은 A, AA, AAA 세 수준으로 나뉜다.

현재 권고안인 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타깃 크기(최소)AA24×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)은 모양으로 전하는 정보·구조·관계를 프로그램이 알아낼 수 있어야 한다고 요구한다. 굵은 글자로 꾸민 문단은 눈에는 제목이지만 낭독기에는 그냥 문단이다.

버튼 요소와 div 요소 비교표. button은 Tab 포커스, Enter와 Space 실행, 버튼 역할, 비활성, 폼 제출을 기본으로 갖고, div에 role만 단 경우는 이 모두를 직접 만들어야 한다<button><div role="button">Tab으로 포커스✓ 기본✗ tabindex="0" 직접Enter·Space로 실행✓ 기본✗ keydown 처리 직접화면 낭독기가 "버튼"✓ 기본✗ role="button"비활성 상태✓ disabled✗ aria-disabled + 막기 직접폼 제출✓ type="submit"✗ 직접 구현ARIA는 역할과 상태를 알릴 뿐 동작을 더하지 않는다. 맞는 HTML 요소가 있으면 그것을 쓴다
<button>과, <div>에 role만 단 가짜 버튼. 오른쪽 열은 모두 직접 만들어야 하고, 하나라도 빠지면 키보드나 낭독기 사용자에게 고장 난 버튼이 된다.

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).

포커스 순서 단계 그림: 상품 이름이 위, 설명이 왼쪽 아래, 장바구니 버튼이 오른쪽 아래에 보이는 화면에서, 첫 마크업은 Tab이 장바구니부터 가고, 고친 마크업은 상품 이름, 설명, 장바구니 순서로 간다상품 이름설명장바구니CSS order로 위치만 옮김123DOM 순서1. 장바구니2. 상품 이름3. 설명어긋남 3곳CSS order로 위치만 옮김123DOM 순서1. 장바구니2. 상품 이름3. 설명어긋남 3곳CSS order로 위치만 옮김123DOM 순서1. 장바구니2. 상품 이름3. 설명어긋남 3곳DOM 순서를 화면 순서와 맞춤123DOM 순서1. 상품 이름2. 설명3. 장바구니어긋남 0곳DOM 순서를 화면 순서와 맞춤123DOM 순서1. 상품 이름2. 설명3. 장바구니어긋남 0곳DOM 순서를 화면 순서와 맞춤123DOM 순서1. 상품 이름2. 설명3. 장바구니어긋남 0곳파란 테두리 = 지금 포커스. 화면 낭독기도 같은 순서로 읽는다
1 / 6

CSS order로 위치만 옮김: Tab 1번째 → "장바구니". 눈으로는 상품 이름이 먼저인데 포커스와 낭독은 장바구니부터 간다(어긋난 자리 3곳).

같은 화면을 두 가지 마크업으로. 위쪽 숫자는 Tab과 화면 낭독기가 지나는 순서(DOM 순서)다. 어긋난 자리 수는 계산 함수로 셌다.

화면 낭독기는 이 DOM 순서로 한 항목씩 읽는다. 안드로이드의 TalkBack은 오른쪽으로 밀면 다음 항목, 왼쪽으로 밀면 이전 항목으로 가고, 두 번 탭하면 선택한 항목을 실행한다(TalkBack 도움말). iOS의 VoiceOver도 손가락을 밀어 항목을 옮기고 두 번 탭해 실행하는 방식이다. 직접 켜서 내 화면을 한 번 끝까지 들어 보는 것이 가장 확실한 검사다. 제목만 골라 듣거나 링크만 골라 듣는 기능도 있어, 제목 구조가 엉망이면 페이지를 훑을 길이 사라진다.

  • 건너뛰기 링크: 페이지 맨 앞에 "본문으로 건너뛰기"를 두면 키보드 사용자가 매번 메뉴를 다 지나지 않아도 된다.
  • 모달은 포커스를 가둔다: 대화상자가 열리면 포커스를 그 안으로 옮기고, 닫으면 연 버튼으로 돌려준다. 열린 동안 Tab이 뒤쪽 페이지로 새어 나가지 않게 한다.
  • 바뀐 내용은 알린다: 화면 일부만 바뀌는 알림(저장됨, 오류 수)은 aria-live 영역에 넣어야 낭독기가 읽는다.

대체 텍스트 쓰는 법

그림을 볼 수 없는 사람에게는 alt가 그 그림이다. 좋은 대체 텍스트는 그림을 묘사하는 것이 아니라 그 자리에서 그림이 하는 일을 대신한다. W3C의 alt 결정 나무는 다섯 질문으로 정리한다.

대체 텍스트 결정 나무. 꾸밈이면 빈 alt, 링크나 버튼 안이면 누르면 일어나는 일, 글자 그림이면 그 글자, 뜻을 전하는 그림이면 그 뜻을 짧게, 복잡한 그림이면 짧은 alt와 본문의 자세한 설명이다꾸밈일 뿐인가?예alt=""(빈 값)구분선, 배경 무늬아니요링크·버튼 안에 있나?예누르면 일어나는 일돋보기 → "검색"아니요글자를 담은 그림인가?예그림 속 글자 그대로로고 글자, 배너 문구아니요뜻을 전하는 사진·아이콘인가?예전하는 뜻을 짧게"비 갠 한강"아니요그래프·도표처럼 복잡한가?예짧은 alt + 본문에 자세히표나 본문으로 수치를같은 그림도 놓인 자리에 따라 답이 다르다
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).

같은 크기의 창 세 개. 100%에서는 1280 CSS 픽셀 폭에 세 열, 200%에서는 640 CSS 픽셀에 두 열, 400%에서는 320 CSS 픽셀에 한 열로 글자가 커지며 다시 흐르고, 가로 스크롤은 생기지 않는다100%1280 CSS px3열 · 가로 스크롤 없음200%640 CSS px2열 · 가로 스크롤 없음400%320 CSS px1열 · 가로 스크롤 없음1.4.4: 글자를 200%까지 키워도 내용과 기능이 그대로1.4.10: 320 CSS px 폭에서 한 방향으로만 스크롤
같은 창을 100%, 200%, 400%로 확대하면 CSS 픽셀 폭이 1280, 640, 320이 된다. 반응형 레이아웃이 잘 되어 있으면 확대는 곧 좁은 화면이라, 열 수가 줄며 다시 흐른다.

확대와 반응형은 같은 문제다. 브레이크포인트를 em으로, 글자를 rem으로 쓰고, 높이를 고정하지 않고, 가로 폭을 고정 픽셀로 묶지 않으면 따로 손대지 않아도 대부분 통과한다. 반대로 height: 40px에 글자를 담은 버튼은 글자를 키우면 잘린다.

흔한 오해

  • "브레이크포인트는 아이폰·아이패드 폭에 맞춘다" — 기기는 해마다 바뀐다. 내용이 어색해지는 폭에 두고 em으로 쓴다.
  • "반응형이면 접근성도 된다" — 좁은 화면에 맞는 것과 키보드·낭독기로 쓸 수 있는 것은 다르다. 둘 다 따로 시험한다.
  • "엄지 영역은 화면 아래쪽이다" — 널리 퍼진 그림을 만든 연구자 스스로 부정확하다고 했다. 사람은 가운데를 가장 많이 보고 누른다.
  • "ARIA를 많이 달수록 접근성이 좋다" — 첫 규칙은 맞는 HTML 요소를 쓰는 것이다. 동작 없는 role은 고장 난 버튼을 만든다.
  • "모든 그림에 자세한 alt를 단다" — 꾸밈 그림은 빈 alt가 맞고, 복잡한 그림은 본문에 설명을 둔다. 그림이 그 자리에서 하는 일을 쓴다.
  • "확대를 막아야 레이아웃이 안 깨진다" — 확대를 막으면 저시력 사용자가 읽지 못한다. 확대해도 다시 흐르게 만드는 것이 해법이다.
  • "자동 검사 도구가 통과하면 된다" — 도구는 대비·빠진 alt 같은 일부만 잡는다. 키보드만으로 한 번, 화면 낭독기로 한 번 직접 써 본다.

핵심 자료

요지는 직접 정리한 한 줄이다.

자료한 줄 요지
web.dev — Learn Responsive Design브레이크포인트는 기기 크기가 아니라 콘텐츠가 깨지는 지점에서 정한다.
MDN — Container queries컴포넌트가 화면이 아니라 담긴 칸의 폭에 반응하게 하는 법.
MDN — Responsive imagessrcset·sizes로 해상도를, picture로 구도를 바꾸는 법과 브라우저의 선택.
Steven Hoober — Design for Fingers, Touch, and People사람은 화면 가운데를 가장 많이 보고 누르며, 가운데 타깃은 더 작아도 된다.
W3C — What's New in WCAG 2.22.2에 더해진 성공 기준 9개와 빠진 4.1.1.
W3C — Using ARIAARIA 첫 규칙: 맞는 HTML 요소가 있으면 그것을 쓴다.
W3C — An alt Decision Tree그림의 쓰임에 따라 alt를 비우거나, 기능·글자·뜻을 적는 질문 다섯.