aria-label을 쓰다 보면 헷갈리는 부분이 있다. 텍스트 콘텐츠와 aria-label을 함께 넣었을 때, 스크린 리더가 둘 다 읽을지 아니면 하나만 읽을지 애매한 것이다.
예를 들어 버튼에 "삭제"라고 써있고 aria-label="이 리뷰 삭제하기"를 추가하면 "이 리뷰 삭제하기 삭제"로 읽힐까? 아니면 둘 중 하나만 읽힐까?
정답은 요소마다 다르다. 어떤 요소는 aria-label이 텍스트를 완전히 대체하고, 어떤 요소는 텍스트와 함께 읽힌다.
aria-label이 텍스트를 대체하는 요소들
상호작용하는 요소들
버튼, 링크, 입력 필드처럼 사용자가 클릭하거나 입력하는 요소들은 aria-label이 기존 텍스트를 대체한다.
<button aria-label="이 리뷰 삭제하기">삭제</button>
<!-- 읽힘: "이 리뷰 삭제하기" -->
<a href="/edit" aria-label="프로필 수정 페이지로 이동">수정</a>
<!-- 읽힘: "프로필 수정 페이지로 이동" -->
여기에 해당하는 요소: <a>, <button>, <input>, <select>, <textarea>
페이지 구조 요소들
헤더, 네비게이션, 섹션 같은 구조를 나타내는 요소들도 마찬가지다.
<nav aria-label="메인 네비게이션">
<ul>...</ul>
</nav>
<!-- 읽힘: "메인 네비게이션" -->
여기에 해당하는 요소: <header>, <nav>, <main>, <footer>, <aside>, <section>, <form>, <fieldset>, <legend>, <iframe>, <img>
이런 요소들은 "무엇을 하는가"가 중요하기 때문에 aria-label로 더 구체적인 설명을 제공하면 기존 텍스트는 무시된다.
aria-label과 텍스트가 함께 읽히는 요소들
반면 제목이나 문단처럼 콘텐츠를 담는 요소들은 다르게 동작한다.
<h2 aria-label="중요 공지:">새로운 기능 안내</h2>
<!-- 읽힘: "중요 공지: 새로운 기능 안내" -->
<p aria-label="참고:">이 기능은 베타 버전입니다.</p>
<!-- 읽힘: "참고: 이 기능은 베타 버전입니다." -->
여기에 해당하는 요소: <h1>~<h6>, <p>, <li>, <dt>, <dd>, <span>, <div> (role이 없을 때)
이런 요소들은 "무엇을 담고 있는가"가 중요하다. 콘텐츠 자체가 의미이기 때문에 aria-label은 추가 정보로만 작용한다.
실제 사용 예시
아이콘만 있는 버튼
<button aria-label="메뉴 열기">
<svg>...</svg>
</button>
아이콘만 있고 텍스트가 없는 버튼에서 유용하다.
맥락을 추가해야 하는 링크
페이지에 "삭제" 링크가 여러 개 있으면 스크린 리더 사용자는 어떤 걸 삭제하는지 구분하기 어렵다.
<a href="/delete" aria-label="댓글 삭제">삭제</a>
이렇게 하면 "댓글 삭제"로 읽혀서 명확해진다.
제목에 강조 추가
<h2 aria-label="필독:">공지사항</h2>
<!-- 읽힘: "필독: 공지사항" -->
제목에 추가 맥락을 넣고 싶을 때 사용할 수 있다.
주의해야 할 점
우선순위
aria-labelledby와 aria-label을 함께 쓰면 aria-labelledby가 우선된다.
<button aria-label="무시됨" aria-labelledby="label-id">버튼</button>
<span id="label-id">실제로 읽힘</span>
img 태그의 특수성
img 태그는 alt 속성이 있으면 aria-label을 무시한다.
<img src="..." alt="사용됨" aria-label="무시됨">
<img src="..." aria-label="사용됨">
다국어 지원
aria-label도 번역해야 한다는 걸 잊지 말자.
<!-- 한국어 -->
<button aria-label="메뉴 열기">
<svg>...</svg>
</button>
<!-- 영어 -->
<button aria-label="Open menu">
<svg>...</svg>
</button>
지원하지 않는 요소
<strong>, <em>, <code> 같은 요소들은 기본적으로 aria-label을 지원하지 않는다. role을 추가하면 사용할 수 있긴 하다.
다음 역할들도 aria-label을 지원하지 않는다: code, caption, deletion, emphasis, generic, insertion, mark, paragraph, presentation/none, strong, subscript, superscript, suggestion, term, time
자주 하는 실수
불필요한 중복
<!-- ❌ -->
<button aria-label="제출 버튼">제출</button>
<!-- 읽힘: "제출 버튼" -->
<!-- ✅ -->
<button>제출</button>
<!-- 읽힘: "제출, 버튼" -->
버튼은 이미 역할로 "버튼"이라고 알려주는데 또 말할 필요가 없다.
너무 긴 설명
aria-label은 짧고 명확해야 한다. 긴 설명이 필요하면 aria-describedby를 쓰는 게 낫다.
<!-- ❌ -->
<button aria-label="이 버튼을 클릭하면 현재 작성 중인 내용이 모두 삭제됩니다. 삭제된 내용은 복구할 수 없으니 주의하세요.">
삭제
</button>
<!-- ✅ -->
<button aria-label="삭제" aria-describedby="delete-desc">
삭제
</button>
<div id="delete-desc" class="sr-only">
삭제된 내용은 복구할 수 없습니다.
</div>
테스트 방법
브라우저와 스크린 리더마다 동작이 조금씩 다를 수 있다. 실제로 테스트해보는 게 제일 확실하다.
- Windows: NVDA (무료) 또는 JAWS
- macOS: VoiceOver (내장)
- 브라우저 확장: 스크린 리더 시뮬레이터
처음엔 복잡해 보이지만 핵심은 간단하다. 상호작용 요소는 대체, 콘텐츠 요소는 추가. 이것만 기억하면 대부분의 상황에서 제대로 쓸 수 있다.
'Frontend' 카테고리의 다른 글
| Zustand로 전역 상태 관리하기 (0) | 2025.11.18 |
|---|---|
| 웹 애플리케이션의 렌더링 방식과 Next.js (0) | 2025.11.08 |
| 타입스크립트 유틸리티 타입 (0) | 2025.11.05 |
| 타입스크립트 타입 시스템: 계층과 호환성 (0) | 2025.10.31 |
| 타입스크립트의 동작 원리 (0) | 2025.10.27 |
