실제로 도움이 되는 ARIA 레이블 개발자 가이드
ARIA 레이블은 마법 같은 접근성 계층이 아닙니다. 잘 사용하면 컨트롤을 이해하기 쉽게 만들지만, 무심코 사용하면 유용한 텍스트를 숨기고 혼란스러운 인터페이스를 만들 수 있습니다.
목차
- ARIA 레이블은 이름을 위한 것이지, 변명을 위한 것이 아닙니다
- 접근 가능한 이름을 쉽게 설명하면
- 첫 번째 규칙: 네이티브 HTML과 보이는 레이블을 우선하세요
- `aria-label`이 적절한 도구인 경우
- `aria-label`이 잘못된 도구인 경우
- 보이는 텍스트가 이미 있다면 `aria-labelledby`를 선택하세요
- `aria-describedby`는 이름이 아니라 도움말 텍스트에 사용하세요
- 반복 컨트롤에는 고유한 이름이 필요합니다
- 모든 것에 레이블을 붙이지 마세요
- 코드만 보지 말고 계산된 이름을 확인하세요
- 실용적인 리뷰 체크리스트
- 좋은 ARIA의 조용한 규율
ARIA 레이블은 이름을 위한 것이지, 변명을 위한 것이 아닙니다
ARIA는 유용하지만, 불명확한 HTML을 땜질하는 용도로 자주 쓰입니다. 바로 그 지점에서 팀들이 문제를 겪습니다.
가장 흔한 예는 aria-label입니다. 보기에는 해롭지 않아 보입니다. 문자열을 추가하고, linter를 통과시키고, 다음 작업으로 넘어가면 됩니다. 하지만 접근 가능한 이름은 장식이 아닙니다. 이는 많은 보조 기술이 사용자가 버튼, 링크, 폼 필드, 제목, 랜드마크, 컨트롤을 탐색할 때 노출하는 이름입니다.
그 이름이 모호하거나, 중복되거나, 오래되었거나, 보이는 레이블과 다르다면 인터페이스는 더 사용하기 어려워집니다. 때로는 더 나쁩니다. aria-label이 DOM에 이미 존재하던 더 나은 텍스트를 덮어쓸 수 있기 때문입니다.
목표는 ARIA를 더 많이 추가하는 것이 아닙니다. 목표는 각 인터페이스 요소의 이름, 역할, 상태, 목적을 명확하게 만드는 것입니다.
접근 가능한 이름을 쉽게 설명하면
대부분의 인터랙티브 요소에는 접근 가능한 이름이 있습니다. 스크린 리더는 그 이름을 사용해 요소가 무엇인지 알립니다.
예를 들어:
<button>Save changes</button>
스크린 리더는 “Save changes, button.”과 같이 읽을 수 있습니다. 역할은 네이티브 button 요소에서 오고, 이름은 그 안의 텍스트에서 옵니다.
이것이 이상적인 경우입니다. 보이는 텍스트와 접근 가능한 이름이 일치합니다.
ARIA 레이블링 속성은 보이는 인터페이스가 완전한 이름을 제공하지 않거나, 이름이 다른 요소에서 와야 할 때 유용해집니다. 주요 속성은 다음과 같습니다.
aria-label: 요소에 직접 문자열을 제공합니다.aria-labelledby: 이름이 될 텍스트를 가진 하나 이상의 요소를 가리킵니다.aria-describedby: 주 이름이 아니라 보조 설명 텍스트를 가리킵니다.
이 세 가지는 관련이 있지만 서로 바꿔 쓸 수는 없습니다.
첫 번째 규칙: 네이티브 HTML과 보이는 레이블을 우선하세요
컨트롤에 보이는 텍스트를 넣을 수 있다면, 먼저 그렇게 하세요.
이것이 더 좋습니다:
<button>Delete invoice</button>
다음보다 낫습니다:
<button aria-label="Delete invoice">
<svg aria-hidden="true" focusable="false">...</svg>
</button>
두 번째 패턴은 아이콘만 있는 버튼에는 유효합니다. 하지만 디자인이 보이는 텍스트를 수용할 수 있다면, 보이는 텍스트는 모두에게 도움이 됩니다. 스크린 리더 사용자, 음성 인식 사용자, 인지 부담이 있는 사람, 빠르게 훑어보는 사람, 번역 도구를 사용하는 사람 모두에게 그렇습니다.
이는 접근성 작업에서 반복해서 등장하는 주제입니다. 네이티브 HTML과 보이는 affordance는 숨겨진 메타데이터보다 더 많은 문제를 해결합니다. 같은 원칙은 버튼 의미론 전반에도 적용됩니다. 팀에서 UI 컨트롤을 점검하고 있다면, 이 가이드와 함께 접근 가능한 웹 버튼을 위한 체크리스트를 참고하기 좋습니다.
aria-label이 적절한 도구인 경우
요소에 접근 가능한 이름이 필요하지만 참조할 만한 보이는 텍스트가 없을 때 aria-label을 사용하세요.
전형적인 사례는 아이콘만 있는 버튼입니다:
<button aria-label="Search">
<svg aria-hidden="true" focusable="false" viewBox="0 0 24 24">
<!-- icon -->
</svg>
</button>
이는 합리적입니다. 보이는 아이콘은 검색을 암시하지만, SVG path 자체는 신뢰할 수 있는 이름을 제공하지 않습니다. aria-label이 그 이름을 제공합니다.
다른 좋은 사례는 다음과 같습니다.
- “X”만으로 표현된 닫기 버튼.
aria-label="Product"처럼 더 구체적인 이름이 필요한 내비게이션 랜드마크.- 보이는 컨텍스트가 버튼 텍스트의 일부가 아닌 반복 컨트롤.
예를 들어:
<nav aria-label="Primary">
...
</nav>
<nav aria-label="Footer">
...
</nav>
둘 다 내비게이션 랜드마크이지만, 이 레이블은 사용자가 랜드마크 단위로 이동할 때 둘을 구분하는 데 도움이 됩니다.
aria-label이 잘못된 도구인 경우
테스트가 요소에 레이블이 필요하다고 말한다는 이유만으로 aria-label을 추가하지 마세요. 먼저 마크업을 고치세요.
나쁨:
<div role="button" tabindex="0" aria-label="Submit">Submit</div>
더 나음:
<button>Submit</button>
첫 번째 예시는 불필요한 작업을 만듭니다. 이제 키보드 동작, 비활성 상태, 폼 동작, 네이티브 버튼이 이미 제공하는 기대 동작을 직접 재현해야 합니다.
또한 보이는 텍스트의 의미를 바꾸는 방식으로 aria-label을 사용해 이름을 바꾸는 것도 피하세요.
<button aria-label="Delete invoice">Remove</button>
사소해 보이지만, 음성 입력에 의존하는 사용자를 혼란스럽게 만들 수 있습니다. 보이는 버튼에는 “Remove”라고 쓰여 있는데 접근 가능한 이름이 “Delete invoice”라면, 사용자가 “click Remove”라고 말했을 때 기대한 결과를 얻지 못할 수 있습니다. WCAG의 “이름에 레이블 포함(label in name)” 요구사항은 바로 이런 이유로 존재합니다. 보이는 텍스트는 일반적으로 접근 가능한 이름에 포함되어야 합니다.
더 나은 버전:
<button aria-label="Remove invoice">Remove</button>
대개는 이쪽이 더 좋습니다:
<button>Remove invoice</button>
보이는 텍스트가 이미 있다면 aria-labelledby를 선택하세요
레이블 텍스트가 이미 페이지에 있다면, 보통 aria-label보다 aria-labelledby가 더 낫습니다.
예:
<h2 id="billing-title">Billing address</h2>
<section aria-labelledby="billing-title">
...
</section>
이제 section의 접근 가능한 이름은 보이는 제목에서 옵니다. 문자열 중복을 피할 수 있고, 이는 번역 실수와 오래된 레이블을 줄입니다.
이는 폼 그룹에서 특히 유용합니다:
<fieldset aria-labelledby="shipping-speed-title">
<legend id="shipping-speed-title">Shipping speed</legend>
<label>
<input type="radio" name="shipping" value="standard">
Standard
</label>
<label>
<input type="radio" name="shipping" value="express">
Express
</label>
</fieldset>
많은 경우 네이티브 legend만으로도 ARIA 없이 충분합니다. 핵심은 보이는 레이블이 우선이어야 한다는 점입니다. ARIA는 기존 의미를 연결해야지, 그 의미의 두 번째 비공개 버전을 만들어서는 안 됩니다.
aria-describedby는 이름이 아니라 도움말 텍스트에 사용하세요
설명은 레이블이 아닙니다.
다음 필드를 보세요:
<label for="password">Password</label>
<input id="password" type="password" aria-describedby="password-help">
<p id="password-help">Use at least 12 characters.</p>
접근 가능한 이름은 “Password”입니다. 설명은 “Use at least 12 characters.”입니다. 스크린 리더가 둘 다 읽을 수 있지만, 둘의 목적은 다릅니다.
이렇게 하지 마세요:
<input type="password" aria-label="Use at least 12 characters">
이는 필드의 이름을 개념이 아니라 지시문으로 짓는 것입니다. 폼을 탐색하는 사용자는 먼저 필드가 무엇인지 알고 싶고, 그다음 어떤 제약이 적용되는지 알고 싶어 합니다.
이 구분은 오류 상태에서도 중요합니다:
<label for="email">Email</label>
<input
id="email"
type="email"
aria-invalid="true"
aria-describedby="email-error"
>
<p id="email-error">Enter an email address in the format [email protected].</p>
레이블은 안정적으로 유지됩니다. 오류 메시지는 보조 컨텍스트가 됩니다.
반복 컨트롤에는 고유한 이름이 필요합니다
목록과 카드는 ARIA 레이블이 필요해지는 경우가 많은 곳입니다.
나쁨:
<button>Delete</button>
<button>Delete</button>
<button>Delete</button>
스크린 리더 사용자가 버튼 단위로 탐색하면 컨텍스트 없이 “Delete, button”을 세 번 들을 수 있습니다.
좋음:
<button aria-label="Delete report: Q4 revenue">Delete</button>
<button aria-label="Delete report: Hiring plan">Delete</button>
<button aria-label="Delete report: Vendor list">Delete</button>
이는 aria-label의 정당한 사용입니다. 보이는 텍스트는 간결하게 유지하면서, 접근 가능한 이름에는 대상이 포함됩니다.
하지만 이 패턴은 신중하게 사용하세요. 대상 이름이 근처에 보인다면 aria-labelledby가 더 유지보수하기 쉬울 수 있습니다:
<article>
<h3 id="report-q4">Q4 revenue</h3>
<button aria-labelledby="delete-q4 report-q4" id="delete-q4">Delete</button>
</article>
접근 가능한 이름은 “Delete Q4 revenue”가 됩니다. 이렇게 하면 속성 안에 보고서 제목을 중복하지 않아도 됩니다.
모든 것에 레이블을 붙이지 마세요
모든 요소에 ARIA 레이블이 필요한 것은 아닙니다.
정적 텍스트는 보통 필요하지 않습니다. 장식용 아이콘도 필요하지 않습니다. 컨테이너도 의미 있는 랜드마크나 위젯 역할이 없는 한 필요하지 않습니다. 과도한 레이블링은 페이지를 시끄럽게 만들고 탐색을 더 어렵게 할 수 있습니다.
이미지에는 이미지에 맞는 모델을 사용하세요. 의미 있는 이미지는 유용한 alt가 필요하고, 장식용 이미지는 빈 alt=""가 필요합니다. 좋은 이미지 텍스트의 대체재로 ARIA 레이블을 사용하지 마세요. 팀에서 이 개념들을 섞고 있다면 실용적인 이미지 alt 텍스트를 다시 살펴보고, 이미지 대체 텍스트와 컨트롤 이름을 분리하세요.
흔한 실수는 모든 SVG에 aria-label을 주는 것입니다. SVG가 버튼 안에 있고 버튼에 이미 이름이 있다면, 아이콘은 보통 보조 기술에서 숨겨야 합니다:
<button aria-label="Open menu">
<svg aria-hidden="true" focusable="false">...</svg>
</button>
그렇지 않으면 브라우저와 보조 기술 조합에 따라 사용자가 중복되거나 이상한 안내를 들을 수 있습니다.
코드만 보지 말고 계산된 이름을 확인하세요
접근성 버그는 마크업이 그럴듯해 보이기 때문에 코드 리뷰를 통과하는 경우가 많습니다.
최신 브라우저 개발자 도구는 계산된 접근성 트리를 보여줄 수 있습니다. Chrome, Edge, Firefox, Safari에서 요소를 검사하고 role, name, description 같은 접근성 정보를 찾아보세요. 확인할 것은 세 가지입니다.
- 역할이 기대한 것인가?
- 접근 가능한 이름이 명확하고 구체적인가?
- 설명이 이름을 대체하지 않으면서 도움이 되는가?
그다음 실제 스크린 리더로 몇 가지 흐름을 테스트하세요. 기본적인 문제를 잡기 위해 전업 보조 기술 전문가가 될 필요는 없습니다. macOS에는 VoiceOver가 내장되어 있습니다. Windows에서는 NVDA가 널리 쓰이며 무료입니다. 모바일에서는 관련이 있을 때 iOS의 VoiceOver와 Android의 TalkBack으로 테스트하세요.
자동화 도구는 유용하지만, “Open,” “Read more,” 또는 “Delete”가 충분한 컨텍스트를 갖는지 안정적으로 판단하지는 못합니다. 자동화는 판사가 아니라 그물로 다루세요. 이는 성능 감사와 비슷합니다. 보고서는 의심스러운 영역을 가리킬 수 있지만, 영향은 여전히 사람이 해석해야 합니다. Lighthouse 보고서를 당황하지 않고 읽는 방법에서 권하는 차분한 접근 방식이 여기에도 적용됩니다.
실용적인 리뷰 체크리스트
ARIA 레이블을 배포하기 전에 다음을 물어보세요.
- 이것을 대신 네이티브 HTML로 만들 수 있는가?
- 레이블로 사용해야 할 보이는 텍스트가 있는가?
- 보이는 텍스트가 있다면, 접근 가능한 이름에 그것이 포함되어 있는가?
- 반복 컨트롤은 시각적 컨텍스트 밖에서 탐색해도 고유한가?
- 도움말 텍스트가 레이블에 억지로 들어가지 않고
aria-describedby로 연결되어 있는가? - 장식용 아이콘이 보조 기술에서 숨겨져 있는가?
- 누군가 브라우저 개발자 도구에서 계산된 접근성 이름을 확인했는가?
- 중요한 흐름에 대해 실제 스크린 리더로 최소 한 번 검증했는가?
이 체크리스트는 대부분의 레이블 문제가 사용자 문제가 되기 전에 잡아냅니다.
좋은 ARIA의 조용한 규율
좋은 ARIA 작업은 드라마틱한 경우가 드뭅니다. 대부분은 절제입니다.
진짜 버튼을 사용하세요. 진짜 레이블을 사용하세요. 보이는 이름과 접근 가능한 이름을 맞추세요. 더 나은 보이는 출처가 없을 때만 aria-label을 추가하세요. 페이지에 이미 올바른 텍스트가 있다면 aria-labelledby를 사용하세요. 보조 지침과 오류에는 aria-describedby를 사용하세요.
웹 플랫폼은 직접 사용할 때 개발자에게 많은 것을 무료로 제공합니다. ARIA는 그 빈틈을 위한 것입니다. 기술은 실제로 빈틈이 있는 때를 아는 데 있습니다.