컴포넌트 패턴
페이지 레이아웃(랜드마크)
header, nav, main, aside, footer, search로 페이지를 큰 영역(랜드마크)으로 나누고, 건너뛰기 링크와 랜드마크 이름으로 원하는 곳에 빠르게 이동하게 하는 방법입니다.
언제 쓰나요
랜드마크는 페이지의 큰 영역(머리글, 메뉴, 본문, 보조 정보, 바닥글 등)을 표시하는 이정표입니다. 화면낭독기 사용자는 랜드마크 목록을 열거나 단축키(예: NVDA의 D, JAWS의 R)로 영역 사이를 바로 건너뛸 수 있습니다. 눈으로 보는 사용자가 화면 배치로 영역을 알아보는 것과 같은 정보를 제공하는 셈입니다.
모든 페이지에 씁니다. 별도의 위젯이 아니라 페이지 마크업의 기본 구조입니다.
HTML 요소가 곧 랜드마크입니다
랜드마크 역할은 HTML 요소로 거의 모두 표현됩니다. <header>→banner, <nav>→navigation, <main>→main, <aside>→complementary, <footer>→contentinfo, <search>→search. role="navigation"처럼 ARIA 역할을 덧붙일 필요는 없습니다. 아주 오래된 브라우저 지원이 필요하거나 <search>를 아직 지원하지 않는 환경에서는 <form role="search">처럼 역할을 명시합니다.
구조
| HTML 요소 | 랜드마크 역할 | 조건·개수 |
|---|---|---|
<header> | banner | article, aside, main, nav, section 안에 있지 않을 때만 banner가 됩니다. 페이지당 보통 하나입니다. |
<nav> | navigation | 주요 탐색 링크 묶음. 여러 개 가능하며, 둘 이상이면 이름으로 구분합니다. |
<main> | main | 페이지의 핵심 콘텐츠. 보이는 main은 하나만 둡니다. |
<aside> | complementary | 본문을 보완하지만 떼어 내도 의미가 통하는 내용(관련 글, 공지 등). |
<footer> | contentinfo | header와 같은 조건에서만 contentinfo가 됩니다. 저작권, 연락처, 정책 링크 등. |
<search> | search | 사이트·페이지 검색 기능을 감쌉니다. |
<section> | region | 이름이 있을 때만(aria-labelledby/aria-label) region 랜드마크가 됩니다. |
<form> | form | 이름이 있을 때만 form 랜드마크가 됩니다. |
- 페이지의 모든 콘텐츠가 어떤 랜드마크 안에 들어가도록 배치하는 것이 좋습니다. 랜드마크 밖에 남은 내용은 랜드마크로 이동하는 사용자가 놓칠 수 있습니다.
- 같은 종류의 랜드마크가 둘 이상이면
aria-label이나aria-labelledby로 서로 다른 이름을 줍니다(예: “주 메뉴”, “바닥글 메뉴”). - 이름에 역할 단어를 넣지 않습니다.
<nav aria-label="주 메뉴 내비게이션">은 “주 메뉴 내비게이션, 내비게이션 랜드마크”처럼 중복해서 읽힙니다. - 랜드마크는 꼭 필요한 큰 영역에만 씁니다. 너무 많으면 목록이 길어져 이동이 오히려 느려집니다.
<body>
<a class="skip-link" href="#main">본문 바로가기</a>
<header>
<p class="logo">하늘도서관</p>
<nav aria-label="주 메뉴">…</nav>
<search>
<form>…</form>
</search>
</header>
<main id="main" tabindex="-1">
<h1>이달의 추천 도서</h1>
</main>
<aside aria-labelledby="notice-h">
<h2 id="notice-h">공지사항</h2>
</aside>
<footer>
<nav aria-label="바닥글 메뉴">…</nav>
</footer>
</body>키보드 동작
랜드마크 자체에는 키 조작이 없습니다. 랜드마크 사이 이동은 화면낭독기 기능이 담당하고, 키보드만 쓰는 사용자(화면낭독기 없이)를 위해서는 건너뛰기 링크를 제공합니다.
| 키 | 동작 |
|---|---|
| Tab (페이지 첫 진입) | 숨어 있던 “본문 바로가기” 링크가 나타나며 포커스를 받습니다. |
| Enter | 건너뛰기 링크를 실행해 반복되는 메뉴를 건너뛰고 main으로 이동합니다. 다음 Tab은 본문 안의 첫 링크·컨트롤로 갑니다. |
| D / Shift + D (NVDA) | 다음·이전 랜드마크로 이동합니다. JAWS는 R, VoiceOver는 로터의 “랜드마크” 항목을 씁니다. |
건너뛰기 링크 만들기
건너뛰기 링크는 페이지의 첫 번째 포커스 가능한 요소로 두고, 평소에는 화면 밖에 두었다가 포커스를 받으면 보이게 합니다. display: none이나 hidden으로 숨기면 포커스를 받을 수 없으니 쓰지 않습니다. 대상인 main에 tabindex="-1"을 주면 일부 브라우저에서 포커스가 확실히 옮겨지고, 다음 Tab 위치도 본문부터 시작합니다.
필요한 ARIA
| 역할·속성 | 붙이는 곳 | 설명 |
|---|---|---|
banner, navigation, main, complementary, contentinfo, search | - | 대응하는 HTML 요소를 쓰면 자동으로 주어집니다. HTML 요소를 쓸 수 없을 때만 role로 지정합니다. |
region | 이름 있는 section | 목록에 꼭 나타나야 할 중요한 영역에만 씁니다. 이름이 없으면 랜드마크가 아닙니다. |
form | 이름 있는 form | 검색 폼이 아닌 주요 폼(예: 회원 가입)을 랜드마크로 노출할 때 이름을 줍니다. |
aria-labelledby | 랜드마크 | 영역 안의 보이는 제목을 이름으로 씁니다. 가능하면 이 방식을 먼저 씁니다. |
aria-label | 랜드마크 | 보이는 제목이 없을 때 이름을 줍니다(예: <nav aria-label="주 메뉴">). |
직접 해 보기
두 미리보기는 겉모습은 거의 같지만 마크업이 다릅니다. “랜드마크 목록 비교” 버튼을 누르면 화면낭독기의 랜드마크 목록처럼 각 문서에서 찾은 랜드마크를 보여 줍니다. 올바른 구현 미리보기 안에서 Tab을 누르면 건너뛰기 링크가 나타나고, Enter로 본문으로 이동할 수 있습니다.
잘못된 구현: div로만 배치
올바른 구현: 랜드마크 요소
실시간 코드
미리보기는 별도 문서(iframe)로 그립니다. 이 페이지 안에 main이나 header를 또 넣으면 실제 페이지의 랜드마크 구조가 어긋나기 때문입니다.
전체 코드
<div class="header">
<div class="logo">하늘도서관</div>
<div class="menu">…</div>
</div>
<div class="content">
<div class="title">이달의 추천 도서</div>
</div>
<div class="footer">…</div><header>
<p class="logo">하늘도서관</p>
<nav aria-label="주 메뉴">…</nav>
</header>
<main id="main" tabindex="-1">
<h1>이달의 추천 도서</h1>
</main>
<footer>…</footer><!doctype html>
<html lang="ko">
<head>
<meta charset="utf-8">
<title>이달의 추천 도서 - 하늘도서관</title>
</head>
<body>
<a class="skip-link" href="#main">본문 바로가기</a>
<header>
<p class="logo"><a href="/">하늘도서관</a></p>
<nav aria-label="주 메뉴">
<ul>
<li><a href="/search">도서 검색</a></li>
<li><a href="/guide">이용 안내</a></li>
<li><a href="/events">행사</a></li>
</ul>
</nav>
<search>
<form action="/search">
<label for="q">도서 검색</label>
<input type="search" id="q" name="q">
<button type="submit">검색</button>
</form>
</search>
</header>
<main id="main" tabindex="-1">
<h1>이달의 추천 도서</h1>
<section aria-labelledby="new-h">
<h2 id="new-h">새로 들어온 책</h2>
…
</section>
</main>
<aside aria-labelledby="notice-h">
<h2 id="notice-h">공지사항</h2>
…
</aside>
<footer>
<nav aria-label="바닥글 메뉴">
<ul>
<li><a href="/privacy">개인정보처리방침</a></li>
<li><a href="/map">찾아오는 길</a></li>
</ul>
</nav>
<p>© 하늘도서관</p>
</footer>
</body>
</html>.skip-link {
position: absolute;
left: 1rem;
top: -3rem; /* 평소에는 화면 밖 */
padding: .5rem 1rem;
background: #0b57d0;
color: #fff;
z-index: 1000;
}
.skip-link:focus {
top: 1rem; /* 포커스를 받으면 화면 안으로 */
}
main:focus { outline: none; } /* tabindex="-1" 대상의 포커스 테두리 정리 (선택) */건너뛰기 링크와 랜드마크는 JavaScript 없이 동작합니다. 단일 페이지 앱(SPA)에서 화면을 바꿀 때는 새 화면의 main이나 제목(h1)으로 포커스를 옮겨 페이지가 바뀌었음을 알립니다.
화면낭독기에서는
| 상황 | 들리는 안내 (예) |
|---|---|
| 페이지에서 처음 Tab | 본문 바로가기, 링크 |
| 랜드마크 단축키로 주 메뉴에 도달 | 주 메뉴, 탐색 랜드마크, 목록 3개 항목 |
| 본문으로 이동 | 메인 랜드마크, 이달의 추천 도서, 제목 수준 1 |
| 보조 영역으로 이동 | 공지사항, 보충 랜드마크 |
| 바닥글 메뉴로 이동 | 바닥글 메뉴, 탐색 랜드마크 |
| 잘못된 구현에서 랜드마크 단축키 | 랜드마크 없음(이동할 곳이 없음) |
점검 체크리스트
- 페이지에
header,nav,main,footer등 알맞은 랜드마크가 있습니다. - 보이는
main이 정확히 하나입니다. - banner·contentinfo로 쓰려는
header·footer가article·section등 안에 들어가 있지 않습니다. - 같은 종류의 랜드마크가 여럿이면 각각 다른 이름이 있고, 이름에 “내비게이션” 같은 역할 단어가 없습니다.
section·form을 랜드마크로 쓰려면 이름을 주었습니다. 랜드마크가 지나치게 많지 않습니다.- 페이지의 주요 콘텐츠가 모두 어느 랜드마크 안에 들어 있습니다.
- “본문 바로가기” 링크가 첫 번째 포커스 요소이며, 포커스를 받으면 화면에 보이고, 누르면
main으로 이동합니다. - HTML 요소와 같은 역할을
role로 중복 지정하지 않았습니다(예:<nav role="navigation">).
관련 WCAG 성공 기준
- 1.3.1 정보와 관계 (A): 영역 구조를 마크업으로 전달합니다.
- 2.4.1 블록 건너뛰기 (A): 건너뛰기 링크와 랜드마크로 반복 영역을 건너뜁니다.
- 2.4.6 제목과 레이블 (AA): 랜드마크 이름이 영역을 잘 설명합니다.
- 2.4.3 포커스 순서 (A)
- 4.1.2 이름, 역할, 값 (A)
관련 가이드: 랜드마크와 제목 구조