HTML — 태그 하나가 기능을 대신한다 · 의미와 접근성 · 이론
div 로 만든 버튼이 잃는 일곱 가지
한 줄 요약
<button> 대신 <div onclick> 을 쓰면 브라우저가 대신 해 주던 일곱 가지를 잃는다. 그리고 대개 그중 다섯 가지는 다시 만들지 않는다.
왜 문제가 되는가 — 보이지 않는 사용자
마우스로 만들고 마우스로 확인하면 이 사고는 끝까지 드러나지 않는다. <div onclick> 은 마우스로는 완벽하게 동작하기 때문이다. QA 도 마우스로 확인하므로 그대로 배포된다.
깨지는 쪽은 키보드만 쓰는 사용자다. 손목을 다쳐서, 스크린리더를 써서, 아니면 그냥 Tab 이 빨라서. 그들에게 그 버튼은 화면에 보이지만 누를 수 없는 그림이다. 그게 결제 버튼이면 그 사람은 결제를 못 한다.
고치는 비용도 시간이 갈수록 커진다. 태그 하나를 바꾸는 일로 끝나지 않고 그 위에 쌓인 CSS 선택자·이벤트 위임·테스트 셀렉터를 전부 따라가야 하는 일이 되기 때문이다. 그래서 이 글은 "나중에 챙길 목록" 이 아니라 태그를 고르는 순간의 판단을 다룬다.
잃는 것들
<div onclick="save()">저장</div> 이 잃는 것.
1. Tab 으로 갈 수 없다 — tabindex="0" 이 필요하다
2. Enter·Space 로 눌리지 않는다 — 키 핸들러를 직접 붙여야 한다
3. 스크린리더가 "버튼" 이라고 읽지 않는다 — 그냥 텍스트다
4. 비활성화가 없다 — disabled 가 안 먹는다
5. 폼 제출이 안 된다 — type="submit" 이 없다
6. 포커스 링이 없다 — 키보드 사용자가 어디 있는지 모른다
7. 브라우저 기본 동작이 없다 — 컨텍스트 메뉴, 자동화 도구 인식
<button> 한 글자를 쓰면 전부 공짜다. 접근성은 나중에 얹는 기능이 아니라 태그를 고르는 순간 대부분 결정된다.
랜드마크 — 페이지의 지도
스크린리더 사용자는 페이지를 위에서부터 읽지 않는다. 랜드마크 사이를 건너뛴다.
<header> 사이트 머리 </header><nav> 탐색 </nav><main> 본문 — 페이지에 하나만 </main><aside> 곁다리 </aside><footer> 꼬리 </footer><main> 이 있으면 "본문으로 건너뛰기" 가 동작한다. <div class="main"> 은 아무 의미가 없다 — 클래스 이름은 기계에게 아무것도 알려 주지 않는다.
제목은 목차다
h1~h6 은 글자 크기가 아니다. 문서의 목차다. 스크린리더는 제목만 훑어 구조를 파악한다.
h1은 하나. 페이지가 무엇에 관한 것인지- 건너뛰지 않는다 —
h2다음에h4가 오면 안 된다 - 크기를 바꾸고 싶으면 CSS 로 바꾼다. 태그를 바꾸지 않는다
h3 이 예뻐서 쓰는 것이 접근성 문제 중 가장 흔하다.
alt 는 "설명" 이 아니다
alt 는 그 이미지가 사라졌을 때 자리를 메울 텍스트다.
| 이미지 | alt |
|---|---|
| 제품 사진 | alt="빨간 등산 배낭 45L" |
| 링크 안의 로고 | alt="홈으로" — 이미지가 아니라 링크의 목적 |
| 장식용 구분선 | alt="" — 비워 둔다 |
| 옆에 같은 설명이 있는 그림 | alt="" — 두 번 읽히면 방해다 |
alt 속성 자체를 빼는 것과 alt="" 는 다르다. 빼면 스크린리더가 파일명을 읽는다(IMG_20240103.jpg). 비워 두면 조용히 건너뛴다. 장식 이미지에는 반드시 alt="" 를 명시한다.
라벨이 없는 입력칸은 이름이 없다
<!-- ❌ placeholder 는 라벨이 아니다 --><input placeholder="이메일"><!-- ✅ --><label for="email">이메일</label><input id="email" type="email" autocomplete="email">placeholder 는 입력하면 사라진다. 무엇을 쓰는 칸이었는지 확인할 방법이 없어진다. 저시력 사용자에게는 대비도 부족하다.
라벨을 연결하면 글자를 눌러도 칸에 포커스가 간다. 모바일에서 특히 큰 차이다.
그리고 type 과 autocomplete 를 제대로 주면 모바일 키보드가 바뀌고 자동완성이 동작한다 — type="email", type="tel", autocomplete="one-time-code".
링크와 버튼은 다르다
> 링크는 이동한다. 버튼은 무언가를 한다.
- 주소가 바뀌면 →
<a href>. 새 탭으로 열기, 북마크, 뒤로 가기가 동작한다 - 상태가 바뀌면 →
<button>
<a href="#" onclick="..."> 은 둘 다 아니다. 그리고 링크 텍스트는 그 자체로 목적을 말해야 한다. 스크린리더 사용자는 링크만 뽑아 목록으로 본다. "여기", "더 보기" 만 스무 개 있는 목록은 쓸모가 없다.
표에는 머리칸이 있어야 한다
<table> <caption>월별 매출</caption> <thead><tr><th scope="col">월</th><th scope="col">매출</th></tr></thead> <tbody><tr><th scope="row">1월</th><td>1,200</td></tr></tbody></table>scope 가 있어야 스크린리더가 "1월, 매출, 1200" 처럼 읽는다. 없으면 숫자만 줄줄 읽힌다.
레이아웃을 위해 표를 쓰지 않는다. 그건 CSS Grid 가 할 일이다.
lang 을 빼먹지 않는다
<html lang="ko">없으면 스크린리더가 한국어를 영어 발음 규칙으로 읽는다. 한 글자 고쳐서 얻는 것 중 효과가 가장 크다.
ARIA 는 마지막 수단이다
> 나쁜 ARIA 는 없는 것만 못하다.
role="button" 을 붙여도 키보드 조작은 생기지 않는다. 겉보기 이름만 바뀌고 실제 동작은 그대로다. 그래서 규칙은 하나다.
적당한 HTML 요소가 있으면 그것을 쓴다. ARIA 는 HTML 에 대응 요소가 없을 때(탭 패널, 트리, 라이브 영역) 쓴다.
현장에서 확인하는 법
자동 검사 도구는 문제의 30~40%만 잡는다. 대비가 모자라다거나 alt 가 없다처럼 기계가 볼 수 있는 것만 잡기 때문이다. 나머지는 사람이 봐야 한다.
- 키보드만으로 끝까지 해 본다. Tab, Shift+Tab, Enter, Space, Esc. 마우스를 치우고 5분이면 대부분 드러난다
- 포커스가 지금 어디 있는지 항상 보이는가
- 200% 확대해도 쓸 수 있는가
이 셋이 자동 도구보다 많이 잡는다.
실무에서는 이것을 PR 체크리스트 한 줄로 굳혀 두는 편이 오래 간다. "새로 만든 상호작용 요소를 키보드로만 써 봤는가" 하나면 충분하다. 사람이 매번 기억하기를 기대하는 규칙은 결국 지켜지지 않는다.