목차13개 항목
출처와 참고
WebAIM의 「Semantic Structure」, 「Headings」, 「Page Titles」, 「Document and Content Language」, 「Skip Navigation Links」, 「Site Searches, Indexes, and Site Maps」, 「Frames and Iframes」, 「Creating Accessible Tables」 등을 바탕으로 WCAG 2.2 기준으로 재구성하고, web.dev 「Learn Accessibility」의 콘텐츠 구조·문서 편으로 보강한 노트다.
목록 쪼개기·<dl> 남용·행 머리글·합계 행 부분은 AOA11Y의 강의 연재 「항공사의 ARIA 적용 사례 살펴보기」를 바탕으로 보강했다.
보조 기술 사용자는 페이지를 어떻게 탐색하나
눈으로 보는 사용자는 페이지를 한눈에 훑고 필요한 곳으로 바로 간다. 스크린 리더 사용자는 그럴 수 없으니 마크업이 만든 구조를 단축키로 건너뛰며 같은 일을 한다. 구조가 없으면 처음부터 끝까지 순서대로 들어야 한다.
| 탐색 방법 | 스크린 리더에서 하는 일 | 무엇이 받쳐 주나 |
|---|---|---|
| 페이지 확인 | 페이지가 열리면 제목부터 듣는다 | <title> |
| 발음 | 언어에 맞는 음성 엔진으로 읽는다 | lang 속성 |
| 제목 훑기 | 다음 제목·특정 수준 제목으로 건너뛴다. 가장 흔한 탐색법이다 | <h1>~<h6> |
| 영역 이동 | 내비게이션·본문·꼬리말로 바로 간다 | 랜드마크(<main>, <nav> 등) |
| 목록 파악 | 「목록, 5개 항목」처럼 개수를 먼저 듣는다 | <ul>, <ol>, <dl> |
| 링크·요소 목록 | 페이지의 링크·제목·영역을 목록 창으로 띄워 고른다 | 의미 있는 링크 텍스트(대체 텍스트, 링크, 쉬운 글) |
| 표 탐색 | 셀을 옮길 때마다 행·열 머리글을 함께 듣는다 | <th> + scope |
키보드만 쓰는 사용자는 이 단축키가 없다. 브라우저는 스크린 리더나 확장 없이 제목·랜드마크 이동을 지원하지 않는다. 그래서 키보드 사용자를 위한 건너뛰기 링크가 따로 필요하다. 태그 자체의 의미는 시맨틱 HTML과 문서 구조에 있고, 이 노트는 그 구조가 탐색 경로로 어떻게 쓰이는지를 다룬다.
페이지 제목: 처음 듣는 한 줄
<title>은 탭과 창 제목에 보이고, 스크린 리더가 페이지를 열 때 가장 먼저 읽는다. 검색 결과의 제목도 된다. WCAG 2.4.2 페이지 제목(A)은 제목이 페이지의 주제나 목적을 설명하도록 요구한다.
| 원칙 | 이유 |
|---|---|
| 내용·목적을 설명한다 | 「Untitled」, 「Home」뿐이면 여러 탭 중 어느 것인지 모른다 |
| 짧게 쓴다 | 페이지마다 매번 읽힌다 |
| 사이트 안에서 고유하게 | 같은 제목의 페이지가 여럿이면 탭·방문 기록·북마크에서 구분되지 않는다 |
<h1>과 같거나 비슷하게 | 둘 다 페이지 주제를 말한다 |
| 구체적인 정보를 앞에 | 탭은 몇 글자만 보이고, 스크린 리더 사용자는 페이지를 옮길 때마다 같은 앞부분을 다시 듣는다. 「시즌 3 | 호박 대회 | 푸드 채널」처럼 좁은 것에서 넓은 것으로 쓴다 |
| 키워드를 채우지 않는다 | 검색 결과는 보통 앞의 50~60자 정도만 보여 준다. 키워드를 나열하면 듣기만 길어진다 |
<title>주문서 | Acme</title> <!-- 좋음: 페이지 고유 정보가 먼저 -->
<title>Acme 웹 접근성 연구소 공식 홈페이지 - 주문서</title> <!-- 탭에서 '주문서'가 잘린다 -->SPA는 라우트가 바뀌어도 문서가 다시 열리지 않으므로 document.title을 직접 바꾼다. 대부분의 프레임워크 라우터에 제목 설정 기능이 있다(Next.js는 metadata).
문서 언어: 올바른 발음
스크린 리더는 선언된 언어의 발음 규칙으로 읽는다. 언어가 없거나 틀리면 한국어를 영어 음성으로 읽어 알아들을 수 없게 된다. 언어 선언은 자동 번역, <q>의 따옴표 모양, 맞춤법 검사 사전 선택에도 쓰인다.
| 성공 기준 | 등급 | 요구 |
|---|---|---|
| 3.1.1 페이지 언어 | A | <html lang>으로 기본 언어를 선언한다 |
| 3.1.2 부분 언어 | AA | 다른 언어로 된 부분에 lang을 따로 준다 |
<html lang="ko">
…
<blockquote lang="en">Nothing about us without us.</blockquote>
<a href="/en/" lang="en">English</a> <!-- 링크 텍스트 자체의 언어를 표시한다 -->
</html>lang이 없으면 스크린 리더는 사용자가 설정한 기본 언어로 읽는다. 영어 음성으로 설정한 사용자에게 한국어 페이지는 알아들을 수 없는 소리가 된다.lang에는 언어 하나만 쓴다.lang="ko,en"처럼 여러 값을 나열하는 방식은 없다. 주 언어를<html>에 두면 모든 자식이 물려받고, 다른 언어 부분만 그 요소에서 덮어쓴다. 덮어쓴 값은 그 요소가 끝날 때까지 유효하다.- 언어 태그는 BCP 47 형식이고, 짧을수록 지원이 좋다. 보통 두 글자 기본 언어 코드(
ko,en,ja)로 충분하다. - 지역 하위 태그(
zh-CN,zh-HK,es-MX)는 서로 알아듣기 어려운 방언을 구분해야 할 때만 쓴다. - 문자 하위 태그(
zh-Latn), 확장 언어(zh-yue), 세 글자 코드는 스크린 리더가 무시하거나 못 읽는 경우가 많다. 써야 하면 실제로 들어 본다. - 링크 텍스트에는 도착 페이지의 언어가 아니라 텍스트 자체의 언어를 표시한다.
- 한국어 문장 속 「Wi-Fi」, 「스마트폰」처럼 이미 익숙한 외래어·고유명사에는 언어 변경을 표시하지 않는다.
<span>같은 인라인 요소의lang은 지원이 고르지 않아, 가능하면<p>,<blockquote>같은 블록 요소에 준다.- 스크린 리더에 해당 언어 음성이 없으면 언어 이름만 알리고 건너뛰기도 한다. PDF 문서도 문서 언어를 따로 설정한다.
제목 계층: 페이지의 목차
제목은 신문의 헤드라인처럼 페이지의 개요를 이룬다. 눈으로 보는 사용자는 훑어 읽고, 스크린 리더 사용자는 제목 사이를 건너뛴다. 계층 규칙(<h1> 하나, 수준 건너뛰지 않기)과 아웃라인 알고리즘 이야기는 시맨틱 HTML과 문서 구조에 있다.
<h1>은 페이지 전체 주제를 말하고, 보통<main>의 시작에 둔다. 머리말·사이드바의 낮은 수준 제목이<h1>보다 앞에 오는 것은 괜찮다.- 본문이
<h1>으로 시작하면 「제목 1로 이동」 한 번으로 본문에 닿는다. 그 자체로 반복 블록 건너뛰기 수단이 된다.
| 흔한 오류 | 문제 | 고치기 |
|---|---|---|
<h1> 없음 | 페이지 주제를 짐작해야 한다 | 주제목을 <h1>으로 |
| 빈 제목 | 내용 없이 「제목」만 읽힌다 | 지우고 간격은 CSS margin으로 |
| 보기에만 제목 | 굵고 큰 <p>는 제목 목록에 없다 | <h2> 등으로 바꾸고 모양은 CSS로 |
| 제목 아닌 것에 제목 태그 | 「레시피 담기」 버튼이 제목으로 읽힌다 | 제목 태그를 빼고 CSS로 꾸민다 |
| 비논리적 순서 | <h1> 다음 바로 <h3>, 또는 전부 같은 수준 | 순서대로 내려간다 |
자동 검사 도구(WAVE의 Structure 패널 등)는 제목 구조를 보여 주지만 그 제목이 적절한지는 사람이 판단한다.
영역(랜드마크): 큰 덩어리 사이 이동
<header>, <nav>, <main>, <aside>, <footer>가 페이지의 큰 영역을 정하고, 스크린 리더는 영역 단위로 이동한다. 태그와 ARIA 랜드마크 역할의 대응표는 시맨틱 HTML과 문서 구조에 있다.
| 규칙 | 이유 |
|---|---|
<main>은 페이지에 하나 | 「본문으로」 이동의 목적지 |
머리말·꼬리말은 <body> 바로 아래에 | <article>, <section>, <main> 등 안에 있는 <header>·<footer>는 배너·정보 랜드마크가 되지 않는다 |
| 모든 콘텐츠를 어떤 영역 안에 | 영역 밖 내용은 영역 이동으로 닿지 않는다 |
<nav>는 주요 내비게이션에만 | 모든 링크 묶음을 <nav>로 감싸면 영역 목록이 시끄러워진다. 꼬리말 안의 링크 묶음에는 보통 필요 없다 |
<nav>가 여럿이면 이름을 준다 | 「내비게이션」이 두 번 나오면 구분할 수 없다 |
사이트 검색 폼은 <search> 또는 role="search" | 검색 영역으로 바로 이동한다 |
<section>·<form>은 이름이 있어야 랜드마크가 된다 | 이름 없는 <section>은 region으로, 이름 없는 <form>은 form 랜드마크로 노출되지 않는다. 영역 이동 목록에 넣고 싶을 때만 aria-label·aria-labelledby를 준다 |
| HTML 요소를 먼저 쓴다 | 랜드마크 요소는 브라우저 지원이 탄탄하다. <nav role="navigation">처럼 둘을 겹쳐 쓰면 대개 해는 없지만 필요도 없다 |
<nav aria-label="주요 메뉴">…</nav>
<nav aria-labelledby="toc-title"><h2 id="toc-title">이 글의 목차</h2>…</nav>aria-label·aria-labelledby로 이름을 주는 규칙은 ARIA와 접근 가능한 이름에서 다룬다.
목록: 개수와 순서를 알린다
스크린 리더는 목록에 들어갈 때 「목록, 항목 5개」를 알리고, 목록 끝으로 건너뛰게 해 준다. 그래서 진짜 목록만 목록으로 표시한다.
| 태그 | 쓸 때 | 예 |
|---|---|---|
<ul> | 순서·중요도가 없는 항목 | 재료 목록, 메뉴 |
<ol> | 순서·순위가 있는 항목 | 조리 순서, 단계 |
<dl> + <dt>·<dd> | 용어와 설명 짝 | 용어집, FAQ |
•나-문자를 찍어 목록처럼 보이게 하지 않는다. 목록이 아닌 내용을 레이아웃 때문에<ul>로 감싸지도 않는다.- 배치 때문에 한 목록을 쪼개지 않는다. 12개 항목을 3열로 보이려고
<ul>넷에 나눠 담으면 「목록 3개」가 네 번 들려 전체가 몇 개인지 알 수 없다. 하나의<ul>에 담고 열 배치는 CSS 그리드로 한다. <dl>은 이름과 값이 한 쌍씩 짝지어질 때만 쓴다. 라디오 묶음·입력 칸·메뉴처럼 정의도 설명도 아닌 것을 구조화했다는 이유로<dl>로 감싸지 않는다. 이미 그렇게 만든 경우에는role="presentation"으로 목록 의미를 지울 수 있지만, 맞는 태그로 바꾸는 것이 우선이다.- Safari는
list-style: none으로 표식을 없앤<ul>·<ol>을<nav>안이 아니면 접근성 트리에서 목록으로 노출하지 않는다. MDN에 따르면 의도된 동작이다. 개수 안내가 중요하면role="list"를 덧붙인다. - 목록은 관련 항목을 한 덩어리로 묶어 보여 주므로 인지·주의력·읽기 장애가 있는 사람에게도 도움이 된다.
건너뛰기 링크: 키보드 사용자의 지름길
키보드·스위치·마우스 스틱 사용자는 본문에 닿기 전에 내비게이션 링크를 하나씩 Tab으로 지나야 한다. 건너뛰기 링크(skip link)는 이 반복을 한 번에 넘긴다. WCAG 2.4.1 블록 건너뛰기(A)는 반복되는 블록을 건너뛸 수단을 요구한다. <main> 영역이나 본문 시작의 <h1>도 기준을 충족하지만, 이 둘은 스크린 리더가 있어야 쓸 수 있으므로 눈으로 보는 키보드 사용자를 위해서는 건너뛰기 링크를 따로 둔다.
<body>
<a class="skip-link" href="#main">본문으로 건너뛰기</a>
<header>…긴 내비게이션…</header>
<main id="main" tabindex="-1">
<h1>…</h1>.skip-link {
position: absolute;
left: 1rem;
top: -100%; /* 평소에는 화면 밖 */
}
.skip-link:focus {
top: 1rem; /* 포커스를 받으면 눈에 띄게 나타난다 */
padding: .75rem 1rem;
background: #000;
color: #fff;
}| 원칙 | 내용 |
|---|---|
| 위치 | 페이지의 첫 번째(또는 그에 가까운) 포커스 가능 요소 |
| 숨기기 | 평소에는 화면 밖에 두고 포커스 시 보이게 한다. display: none, hidden, 크기 0, 배경색과 같은 글자색, 투명도로 숨기면 포커스가 안 가거나 보이지 않는다 |
| 문구 | 목적지를 말하는 「본문으로 건너뛰기」가 「내비게이션 건너뛰기」보다 낫다 |
| 개수 | 보통 하나면 충분하다. 본문 앞에 링크가 몇 개 없으면 생략해도 된다 |
| 목적지 | href와 같은 id를 가진 요소. 일부 브라우저에서 포커스가 확실히 옮겨지도록 목적지에 tabindex="-1"을 준다 |
| 다른 용도 | 긴 링크 목록, 복잡한 표, 소셜 피드처럼 지나가기 버거운 덩어리를 건너뛸 때도 쓴다 |
화면 밖으로 숨기는 기법 자체는 접근성을 위한 CSS와 타이포그래피에 있다. 빠르게 Tab을 누르면 잠깐 나타난 링크를 놓치기 쉬우므로 눈에 띄는 모양을 준다.
시각 순서와 소스 순서를 맞춘다. CSS로 본문을 내비게이션보다 위에 그리고 소스에서는 내비게이션을 먼저 두면, 키보드·스크린 리더 순서와 보이는 순서가 어긋나 혼란을 준다(WCAG 1.3.2 의미 있는 순서, 2.4.3 포커스 순서). 대화 상자처럼 새로 나타나는 콘텐츠로 포커스를 옮기는 일은 키보드 접근성과 포커스 관리에서 다룬다.
사이트 검색과 사이트맵: 여러 갈래의 길
주요 내비게이션이 쓰기 어려운 사람에게는 다른 길이 필요하다. WCAG 2.4.5 여러 가지 방법(AA)은 일련의 페이지 안에서 한 페이지를 찾는 방법을 두 가지 이상 요구한다. 검색, 사이트맵, 목차 중 하나를 내비게이션에 더하면 된다.
사이트 검색
- 모든 페이지의 같은 자리에 둔다. 별도 검색 페이지로 한 번 더 이동하게 하지 않는다.
- 입력란과 버튼에 분명한 이름을 준다. 다른 폼과 헷갈리지 않아야 한다.
- 입력란은 25~35자 정도 보이게 넓혀 검색어를 보며 고칠 수 있게 한다.
- 기본은 단순 검색이고, 고급 검색은 선택지로 둔다.
<search>
<form action="/search">
<label for="q">사이트 검색</label>
<input id="q" name="q" type="search">
<button>검색</button>
</form>
</search><search> 요소는 role="search"와 같은 검색 랜드마크다. 폼 라벨 규칙은 접근 가능한 폼과 오류 처리에 있다.
사이트맵·색인
| 종류 | 장점 | 주의 |
|---|---|---|
| 가나다(알파벳) 색인 | 책의 색인처럼 용어로 찾는다 | 사이트가 크면 글자별로 페이지를 나눈다 |
| 주제별(구조) 사이트맵 | 사이트 구조를 한눈에 본다 | 실제 내비게이션 구조와 같게 짠다 |
| 그림 사이트맵 | 조직도처럼 시각적이다 | 복잡해지기 쉽고 대체 텍스트를 쓰기 어렵다. 쓰면 텍스트 사이트맵을 함께 둔다 |
iframe: 이름표를 단다
<iframe>은 다른 문서를 페이지 안에 넣는다. 스크린 리더는 iframe의 내용을 마크업 위치에서 부모 페이지의 일부처럼 읽고, iframe 단위로 이동하기도 한다.
- 모든 iframe에 짧은
title을 준다. 스크린 리더가 iframe의 이름으로 읽는다. 동영상 플레이어, 지도, 광고처럼 따로 구분되는 덩어리일수록 중요하고, 이름 없는 iframe은 자동 검사 도구도 오류로 잡는다. - 사용자가 글자를 키우면 내용이 넘치므로 스크롤을 막지 않는다. 옛
scrolling="no"속성은 HTML에서 폐기됐다. 크기는 고정 픽셀보다 상대 단위나aspect-ratio로 준다. - 화면 배치를 위해 iframe을 쓰지 않는다. 배치는 CSS로 한 페이지에서 한다. iframe은 외부 콘텐츠를 들여올 때 쓴다.
<iframe src="https://www.youtube.com/embed/…" title="접근성 개론 강의 동영상"
style="width: 100%; aspect-ratio: 16 / 9"></iframe>과거에는 <frameset>으로 여러 페이지를 한 화면에 나눴다. 프레임은 HTML에서 폐기됐고 새로 쓰지 않는다.
표: 셀마다 머리글을 듣게 한다
눈으로 보는 사용자는 셀을 보며 위의 열 머리글과 왼쪽 행 머리글을 저절로 연결한다. 스크린 리더 사용자에게는 이 연결을 마크업으로 준다. 그러면 셀을 옮길 때마다 「나이, 재키, 5」처럼 머리글과 값을 함께 듣는다(WCAG 1.3.1 정보와 관계).
데이터 표
<table>
<caption>지점별 분기 매출</caption>
<tr>
<th scope="col">지점</th>
<th scope="col">1분기</th>
<th scope="col">2분기</th>
</tr>
<tr>
<th scope="row">서울</th>
<td>120</td>
<td>140</td>
</tr>
</table>| 요소·속성 | 규칙 |
|---|---|
<caption> | 표의 이름. <table> 바로 다음 첫 자식으로 둔다. 필수는 아니지만 대부분 도움이 된다 |
<th> / <td> | 머리글 셀과 데이터 셀을 구분한다. 머리글은 비우지 않는다 (왼쪽 위 모서리 셀이 흔한 실수) |
scope="col" / "row" | 머리글이 열인지 행인지 알린다. 브라우저가 추측하게 두지 말고 <th>마다 붙인다. 여러 칸을 합친 머리글에도 통한다 |
headers + id | 각 <td>가 머리글 id들을 나열한다. scope로 연결이 틀어질 만큼 복잡한 표에서만 쓴다. 마크업이 많고 깨지기 쉽다 |
<thead>, <tbody>, <tfoot> | 접근성 효과는 거의 없다. 스타일링과 긴 표 인쇄에 쓴다 |
- 복잡한 표는 단순하게 쪼갠다. 합친 셀과 여러 단계 머리글은 스크린 리더마다 처리가 고르지 않다. 구조를 따로 설명해야 할 정도면 너무 복잡한 표다.
- 열 머리글만이 아니라 행을 식별하는 열(탑승자 이름, 여정)도
<th scope="row">로 만든다. 셀로 이동할 때 「두 번째 초과 수하물, 엄마, 방콕–인천, 1개」처럼 행·열 머리글이 함께 들린다. - 열 머리글과 상관없는 합계(「총액 100,000원」)는 표 안
<tfoot>보다 표 뒤의 문단으로 뺀다. 표 안에 두면 합계를 읽을 때 무관한 열 머리글까지 붙어 들린다. - 옛
summary속성은 HTML에서 폐기됐다. 표에 설명이 필요하면<caption>이나 표 앞의 본문,aria-describedby로 준다. - 너비는 고정 픽셀보다 브라우저에 맡기거나 상대 단위로 주고, 셀 높이를 고정하지 않는다. 글자를 키우면 내용이 넘친다.
- 좁은 화면에서 가로로 스크롤하는 표는 감싸는 요소에
tabindex="0"과 이름을 주어 키보드로 스크롤할 수 있게 한다. 표 요소의display를 CSS로 바꾸면 일부 브라우저가 표 의미를 잃기도 하므로 접근성 트리로 확인한다.
<div role="region" aria-labelledby="sales-cap" tabindex="0" style="overflow-x: auto">
<table><caption id="sales-cap">지점별 분기 매출</caption>…</table>
</div>레이아웃 표
배치를 위해 쓰는 표다. 지금은 CSS 그리드·플렉스박스로 배치하고 표를 쓰지 않는다. 다만 HTML 이메일·뉴스레터·광고에는 아직 레이아웃 표가 흔하다. 이런 표는 이렇게 다룬다.
- 스크린 리더는 레이아웃 표를 소스 순서대로 읽는다(선형화, linearization). 셀을 합치면 보이는 순서와 읽는 순서가 쉽게 어긋난다.
<caption>,<th>,scope를 넣지 않는다. 데이터 표로 오인해 행·열 개수를 알리며 혼란을 준다.role="presentation"으로 표 의미를 지울 수 있다.- 중첩 표, 불필요한 행·열과 셀 합치기를 피한다. 테두리를 0으로 두면 눈에는 평범해 보여도 스크린 리더에는 복잡한 구조로 들린다.
정리
- 보조 기술 사용자는 제목·영역·링크 목록으로 건너뛰며 읽는다. 구조 마크업이 곧 탐색 경로다.
- 페이지가 열리면
<title>을, 읽을 때는lang을 쓴다. 제목은 고유한 정보를 앞에, 언어는 짧은 태그로 준다. <h1>하나에서 내려가는 제목 계층과<main>하나를 갖춘 영역이 기본 뼈대다.<nav>가 여럿이면 이름을 준다.- 키보드 사용자에게는 스크린 리더 단축키가 없으니 포커스 시 보이는 건너뛰기 링크를 둔다.
- 내비게이션 말고도 검색·사이트맵 같은 다른 길을 둔다.
- iframe에는
title, 데이터 표에는<caption>과scope를 단<th>를 준다. 복잡한 표는 쪼갠다.
관련 노트: 시맨틱 HTML과 문서 구조, 웹 접근성, 키보드 접근성과 포커스 관리, ARIA와 접근 가능한 이름, 대체 텍스트, 링크, 쉬운 글, 스크린 리더로 테스트하기
원본 기록13
- 원문 (새 창) webaim.org/techniques/semanticstructure/
- 원문 (새 창) webaim.org/techniques/headings/
- 원문 (새 창) webaim.org/techniques/pagetitle/
- 원문 (새 창) webaim.org/techniques/language/
- 원문 (새 창) webaim.org/techniques/skipnav/
- 원문 (새 창) webaim.org/techniques/sitetools/
- 원문 (새 창) webaim.org/techniques/frames/
- 원문 (새 창) webaim.org/techniques/tables/
- 원문 (새 창) webaim.org/techniques/tables/data
- 원문 (새 창) web.dev/learn/accessibility/structure
- 원문 (새 창) web.dev/learn/accessibility/more-html
- 원문 (새 창) developer.mozilla.org/en-US/docs/Web/CSS/Reference/Properties/list-style
- 원문 (새 창) www.youtube.com/playlist?list=PLtaz5vK7MbK0wauPYD9VtgkwX2Bx_Ygo3