실습: 17점짜리 페이지를 100점으로 고쳐 보기
접근성 글을 여러 편 읽어도 "그래서 내 페이지에는 어떻게 적용하지?"가 막막할 수 있습니다. 이 글은 일부러 결함을 심은 작은 쇼핑몰 페이지(가상의 예시입니다)를 이 사이트의 검사 엔진에 실제로 돌려 나온 결과를 그대로 싣고, 문제를 하나씩 고쳐 점수가 올라가는 과정을 따라갑니다. 수정 코드는 내 사이트에 그대로 옮겨 쓸 수 있는 패턴으로 골랐고, 각 수정의 배경 원리는 관련 아티클로 연결해 두었습니다.
1. 수정 전 페이지
상품 배너, 알림 신청 폼, 구매 버튼, 재고 표가 있는 단순한 페이지입니다. 겉으로는 평범해 보입니다.
<html>
<head>
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
<style>
body { background: #ffffff; color: #333333; font-size: 16px; }
.sub { color: #aaaaaa; font-size: 14px; }
.btn { background: #f4b400; color: #ffffff; font-size: 16px; }
a:focus, button:focus { outline: none; }
</style>
</head>
<body>
<div class="top">
<img src="/logo.png">
<a href="/cart"><svg width="20" height="20"><path d="M0 0h20v20z"/></svg></a>
</div>
<h3>오늘의 특가</h3>
<img src="/banner.jpg" alt="image">
<p class="sub">배송은 평일 오후 2시 이전 주문 건까지 당일 출고됩니다.</p>
<form>
<input type="text" name="email" placeholder="이메일">
<input type="text" name="tel" placeholder="연락처">
<button class="btn"><img src="/arrow.png" alt=""></button>
</form>
<div class="buy" onclick="buy()">바로 구매</div>
<a href="#" >더보기</a>
<div id="menu" tabindex="3">메뉴</div>
<div id="menu">중복</div>
<table><tr><td>사이즈</td><td>재고</td></tr><tr><td>M</td><td>3</td></tr></table>
</body>
</html>
2. 검사 결과: 17점, 19건
검사 엔진이 반환한 결과를 심각도 순으로 정리하면 다음과 같습니다. (줄 번호는 위 코드 기준)
| 심각도 | 규칙 | 위치 | 무슨 문제인가 |
|---|---|---|---|
| 치명적 | html-lang-missing | 1행 html | 문서 언어가 없어 한국어를 영어 음성으로 읽을 수 있음 |
| 치명적 | title-missing | 2행 head | 탭·낭독기가 읽을 페이지 제목이 없음 |
| 치명적 | img-alt-missing | 13행 로고 이미지 | alt 속성 자체가 없어 파일명이 읽힘 |
| 치명적 | link-no-name | 14행 장바구니 아이콘 링크 | 아이콘만 있는 링크라 이름이 없음 |
| 치명적 | button-no-name | 22행 화살표 버튼 | 장식 이미지(alt="")만 있어 이름이 없음 |
| 치명적 | click-not-focusable | 24행 "바로 구매" | div onclick: 탭으로 접근 불가, Enter로 실행 불가 |
| 높음 | contrast-low | 18행 안내 문구 | #aaaaaa / #ffffff = 2.32:1 (기준 4.5:1) |
| 높음 | focus-outline-removed ×2 | CSS a:focus, button:focus | 포커스 테두리를 지워 위치를 알 수 없음 |
| 높음 | positive-tabindex | 26행 메뉴 | tabindex="3"으로 탭 순서가 뒤엉킴 |
| 높음 | viewport-no-zoom | 3행 meta viewport | 확대 차단(user-scalable=no) |
| 보통 | duplicate-id | 27행 | id="menu" 중복 |
| 보통 | form-placeholder-only ×2 | 20·21행 입력창 | placeholder만으로 레이블 대신 |
| 보통 | heading-none | 본문 | h1이 없음 (h3만 존재) |
| 보통 | img-alt-suspicious | 17행 배너 | alt가 "image"라 내용 설명이 아님 |
| 보통 | link-vague-text | 25행 | "더보기"만으로는 목적지를 알 수 없음 |
| 보통 | table-no-header | 28행 표 | th 없는 표 |
| 낮음 | landmark-missing | 본문 | main 영역 표시 없음 |
분야별로 보면 구조·탐색 54점, 키보드 조작 56점, 버튼·링크 이름 60점이 특히 낮았고, 색상 대비(91점)와 입력창(91점)은 문제 수가 적어 상대적으로 높았습니다. 점수는 문제 개수보다 심각도에 민감합니다. 치명적 문제 6건만으로 이미 48점이 깎입니다(치명적 1건당 8점, 이 도구 기준).
3. 고치는 순서: 치명적 → 높음 → 보통 → 낮음
한꺼번에 다 고치려 하지 말고 영향이 큰 것부터 처리합니다. 특히 <head>의 세 줄은 한 번에 끝나고 효과가 페이지 전체에 미칩니다.
3-1. 문서 기본 정보 (치명적 2 + 높음 1) — 3줄
<!DOCTYPE html>
<html lang="ko"> ← 언어
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1"> ← 확대 허용
<title>오늘의 특가 - 예시 쇼핑몰</title> ← 페이지 제목
3-2. 이름 없는 이미지·링크·버튼 (치명적 3)
<!-- 전 -->
<img src="/logo.png">
<a href="/cart"><svg width="20" height="20">…</svg></a>
<button class="btn"><img src="/arrow.png" alt=""></button>
<!-- 후 -->
<img src="/logo.png" alt="예시 쇼핑몰 홈">
<a href="/cart" aria-label="장바구니"><svg width="20" height="20" aria-hidden="true">…</svg></a>
<button class="btn"><img src="/arrow.png" alt="">알림 신청</button>
세 곳 모두 "이름이 없다"는 같은 병입니다. 이미지는 alt로, 아이콘 링크는 aria-label로, 버튼은 눈에 보이는 텍스트("알림 신청")로 이름을 붙였습니다.
버튼의 화살표 이미지는 장식이므로 alt=""를 유지해 중복 낭독을 피했습니다.
(원리는 대체 텍스트 가이드와 ARIA 가이드 참고)
3-3. 키보드로 못 누르는 "바로 구매" (치명적 1)
<!-- 전 -->
<div class="buy" onclick="buy()">바로 구매</div>
<!-- 후 -->
<button type="button" class="buy" onclick="buy()">바로 구매</button>
div에 tabindex, role, 키 이벤트를 하나씩 붙이는 대신 요소만 button으로 바꿨습니다.
이 한 줄로 탭 이동, Enter·Space 실행, 역할 전달이 모두 해결됩니다. 이 실습에서 가장 이득이 큰 수정입니다.
3-4. 눈에 안 보이는 포커스와 뒤엉킨 순서 (높음 3)
/* 전: 포커스 테두리 삭제 */
a:focus, button:focus { outline: none; }
/* 후: 마우스 클릭 때는 조용히, 키보드 이동 때는 또렷하게 */
a:focus-visible, button:focus-visible {
outline: 3px solid #0f5f8f;
outline-offset: 2px;
}
tabindex="3"과 중복 id="menu"가 걸린 두 <div>는 예시용 자리표시자라 제거했습니다.
실제 메뉴라면 <button>·<a href>로 만들어 tabindex 없이 마크업 순서 = 탭 순서가 되게 하고, id는 페이지 안에서 하나씩만 씁니다.
3-5. 색상 대비 (높음 1)
.sub { color: #aaaaaa; } /* 흰 배경 대비 2.32:1 ✗ */
.sub { color: #595959; } /* 흰 배경 대비 7.00:1 ✓ (AAA까지 통과) */
4.5:1만 넘기면 통과지만, 회색 계열은 한 단계 짙게 잡으면 여유가 생깁니다. 버튼 배경도 #f4b400(흰 글씨와 1.85:1)에서
#8a5a00(흰 글씨와 5.93:1)로 미리 바꿨습니다. 이 버튼은 수정 전에는 안에 글자가 없어 대비 검사가 걸리지 않았지만,
글자를 넣는 순간 문제가 될 조합이었습니다. (원리는 색상 대비 가이드)
3-6. 입력창 레이블·제목·링크·표·영역 (보통 7 + 낮음 1)
<!-- 입력창: label을 붙이고 type과 autocomplete를 맞춘다 -->
<label for="email">이메일</label>
<input type="email" id="email" name="email" autocomplete="email">
<label for="tel">연락처</label>
<input type="tel" id="tel" name="tel" autocomplete="tel">
<!-- 제목: h3 → h1 -->
<h1>오늘의 특가</h1>
<!-- 배너 alt: 이미지가 전달하는 정보를 문장으로 -->
<img src="/banner.jpg" alt="가을 시즌 아우터 최대 40% 할인">
<!-- 링크: 목적지를 담은 문구 -->
<a href="/products/outer">아우터 상품 전체 보기</a>
<!-- 표: th + scope -->
<table><tr><th scope="col">사이즈</th><th scope="col">재고</th></tr>…</table>
<!-- 본문 영역 -->
<main id="main"> … </main>
입력창에는 type을 email, tel로 맞추면 모바일에서 알맞은 키보드가 뜨는 부수 효과도 있습니다.
헤더에는 함께 "본문 바로가기" 링크와 <header>를 추가했습니다(구조 가이드).
4. 결과: 100점, 0건
수정한 페이지를 같은 엔진으로 다시 검사하면 100점, 문제 0건이 나옵니다. 하지만 여기서 멈추면 안 됩니다.
5. 100점이 "접근성 100%"는 아니다
이 실습의 진짜 교훈은 오히려 여기에 있습니다. 자동 검사가 만점을 준 페이지에도 다음은 확인되지 않았습니다.
- alt 문구가 정말 적절한가 — "가을 시즌 아우터 최대 40% 할인"이 배너가 전달하는 정보의 전부인지는 사람이 이미지를 보고 판단해야 합니다.
- 탭 이동 순서가 화면의 읽는 순서와 맞는가 — 구조는 문법상 맞아도 CSS로 시각 순서를 바꾸면 어긋날 수 있습니다.
- 화면낭독기로 끝까지 조작해 보았는가 — 폼 오류 메시지 안내, 구매 후 결과 안내(라이브 영역)는 검사 대상이 아닙니다.
- 확대 400%·텍스트 간격 변경에서 깨지지 않는가 — 정적 분석으로는 알 수 없습니다. (확대·터치·움직임 가이드)
- JavaScript로 나중에 그려지는 화면 — 이 도구는 서버가 받은 HTML/CSS를 정적으로 분석하므로, 실행 후에 생기는 요소는 보지 못합니다.
그래서 이 도구는 점수 옆에 항상 "법적 준수 여부를 보증하지 않는다"는 문구를 표시합니다. 자동 검사는 기본기 점검과 회귀 방지에 쓰고, 실제 접근성은 사람이 쓰는 방식으로 확인해야 합니다. 업계에서는 자동 도구가 잡는 문제를 전체의 일부(대략 30~40%)로 보는 것이 일반적입니다.