WCAG 성공 기준 · Level A
WCAG 2.5.3: 이름에 포함된 레이블
WCAG 2.5.3은 보이는 텍스트 레이블이 있는 대화형 구성 요소가, 그 보이는 텍스트를 포함하는 접근 가능한 이름을 갖도록 요구합니다. 이는 음성 입력 사용자가 화면에 보이는 내용을 말함으로써 컨트롤을 활성화할 수 있도록 보장하기 위한 것입니다. 보이는 레이블과 접근 가능한 이름이 일치하지 않으면 음성 제어 내비게이션이 깨지고, 수백만 사용자의 신뢰를 훼손하게 됩니다.
- Level A
이 규칙의 의미
\nWCAG 2.5.3 — Label in Name — 은 보이는 텍스트 레이블이 있는 모든 사용자 인터페이스 구성요소에 적용된다. 이 기준은 해당 구성요소의 접근 가능한 이름이 화면에 보이는 레이블 텍스트와 정확히 일치하거나, 최소한 부분 문자열로서 그 보이는 레이블 텍스트를 포함할 것을 요구한다. 이는 사용자가 화면에서 하나의 레이블을 보지만, 보조 기술이나 음성 인식 소프트웨어는 내부적으로 완전히 다른 이름을 인식하는 상황을 방지한다.
\n접근 가능한 이름은 접근성 트리에 노출되어 스크린 리더, 음성 제어 소프트웨어 및 기타 보조 기술에서 사용하는 이름이다. 이 이름은 요소의 보이는 텍스트 콘텐츠, aria-label 속성, aria-labelledby 참조, title 속성, 연결된 <label> 요소 등 다양한 출처에서 올 수 있다. 개발자가 보이는 레이블 텍스트를 포함하지 않는 값으로 접근 가능한 이름을 재정의하면, 불일치가 발생하고 이 기준을 충족하지 못한다.
영향을 받는 요소에는 텍스트를 표시하고 동시에 접근 가능한 이름을 가진 모든 인터랙티브 구성요소가 포함된다. 예를 들어 버튼, 링크, 보이는 레이블이 있는 폼 입력, 메뉴 항목, 탭, 체크박스, 라디오 버튼, 커스텀 ARIA 위젯 등이 있다. 단락이나 제목과 같은 비인터랙티브 요소는 이 기준의 적용 대상이 아니다.
\n통과로 인정되는 경우: 접근 가능한 이름이 앞뒤 공백을 무시했을 때, 보이는 레이블 텍스트를 연속된 부분 문자열로 포함한다. 대소문자는 구분하지 않는다. 예를 들어 버튼에 "Search Products"라고 적혀 있고, 접근 가능한 이름이 "Search Products Now"라면 "Search Products"를 포함하므로 통과한다. 접근 가능한 이름이 "Find Products"라면 보이는 텍스트를 포함하지 않으므로 실패한다.
\n실패로 인정되는 경우: 접근 가능한 이름이 보이는 레이블과 완전히 다르거나, 접근 가능한 이름이 보이는 레이블의 의미 있는 일부를 생략한 경우이다. 시각적으로 "Buy Now"라고 표시된 버튼에 aria-label='Purchase item'이 설정되어 있다면 이 기준을 충족하지 못한다. 마찬가지로 "Contact Us"라고 표시된 링크가 aria-label='Reach our team'이 설정된 요소로 감싸져 있다면 역시 실패이다.
WCAG에서 정의한 공식 예외: 레이블이 순수하게 상징적이거나 그림 문자로만 되어 있고 텍스트 등가물이 없는 구성요소(예: 보이는 텍스트가 전혀 없는 아이콘 전용 버튼)에는 이 기준이 적용되지 않는다. 또한 텍스트가 의미를 가지지 않는 순수 장식 요소의 일부인 경우에도 적용되지 않는다. 수학 표기법이나, 텍스트 표현이 발화 가능한 단어에 매핑될 수 없는 언어 특수 상황도 제외된다. 추가로, 접근 가능한 이름이 의도적으로 더 많은 문맥을 포함하도록 확장되었더라도, 그 안에 보이는 텍스트가 그대로 포함되어 있다면 준수한 것으로 본다.
\n\n왜 중요한가
\n이 기준은 주로 Dragon NaturallySpeaking(현재 Dragon Professional), Windows Speech Recognition, Apple의 Voice Control과 같은 음성 인식 소프트웨어에 의존하는 사용자를 보호한다. 이 사용자는 화면에 보이는 레이블을 말함으로써 인터페이스 요소를 탐색하고 활성화한다. 접근 가능한 이름이 보이는 레이블과 일치하지 않으면, 소프트웨어는 사용자가 말한 이름으로 대상 요소를 찾을 수 없고, 그 컨트롤은 마우스나 키보드 없이는 사실상 도달 불가능해진다. 반복성 긴장 장애, 근이영양증, 뇌성마비, 척수 손상 등 운동 장애가 있는 사용자에게 음성 입력은 종종 컴퓨터를 조작하는 주된 수단이거나 유일한 수단이다. 단 하나의 버튼에서 불일치가 발생해도 전체 작업 흐름에 접근하지 못하게 될 수 있다.
\n스크린 리더 사용자도 영향을 받는다. 스크린 리더가 화면에 보이는 것과 다른 접근 가능한 이름을 읽어 주면 인지적 혼란이 발생한다. 예를 들어 저시력으로 시각적·청각적 피드백을 동시에 사용하는 시력이 있는 사용자는, 화면에서 읽는 내용과 스크린 리더가 읽어 주는 내용이 서로 다르면 인터페이스에 대한 정신적 모델이 깨진다.
\n인지 장애가 있는 사용자에게도, 자신이 읽는 것과 들리는(또는 안내되는) 내용이 일관된다는 점은 큰 도움이 된다. 예기치 않은 이름 변경은 인지적 부담을 증가시키고, 특히 기억력에 어려움이 있거나 처음 시스템을 배우는 사용자에게 혼란이나 오류를 유발할 수 있다.
\n구체적인 실제 시나리오: "Place Order"라는 텍스트가 시각적으로 표시된 버튼이 있는 전자상거래 결제 페이지를 생각해 보자. 개발자가 더 설명적인 이름이라고 판단해 aria-label='Submit purchase form'을 추가한다. Dragon NaturallySpeaking을 사용하는 고객이 "Click Place Order"라고 말하면, Dragon은 접근성 트리를 스캔하지만 "Place Order"라는 이름의 요소를 찾지 못해 버튼을 활성화할 수 없다. 고객은 마우스 조작으로 전환하지 않는 이상 구매를 완료할 수 없는데, 그럴 수 없는 경우도 있다. 결국 거래를 포기한다. 이는 가상의 극단적 사례가 아니라, 소매 및 은행 웹사이트에 대한 자동화 감사에서 흔히 발견되는 실패 패턴이다.
세계보건기구에 따르면 전 세계적으로 10억 명이 넘는 사람이 어떤 형태로든 장애를 가지고 살아간다. 2023년 WebAIM Million 보고서에 따르면, WCAG 2.5.3 레이블 불일치는 감사된 페이지의 의미 있는 비율에서 발견되었으며, 종종 의도는 좋았지만 잘못 적용된 ARIA로 인해 도입되었다. 접근성 외에도, 일관된 레이블링은 SEO를 개선하는데, 링크와 버튼 텍스트를 관련성 순위를 위해 인덱싱하는 검색 엔진 크롤러가 접근 가능한 이름과 보이는 텍스트가 정렬되어 있을 때 더 의미 있는 신호를 받기 때문이다.
\n\n관련 Axe-core 규칙
\n- \n
- label-content-name-mismatch: 이것이 WCAG 2.5.3과 연관된 기본 자동화 규칙이다. 이 규칙은 버튼, 링크,
role='button',role='link',role='menuitem',role='tab'과 같은 ARIA 역할 등, 보이는 텍스트 레이블과 접근 가능한 이름을 모두 가진 인터랙티브 요소를 검사한다. 접근 가능한 이름이 보이는 텍스트를 부분 문자열로 포함하지 않는(대소문자 구분 없음) 요소를 모두 표시한다. 이 규칙은 특히aria-label이나aria-labelledby로 접근 가능한 이름이 화면상의 텍스트와 다르게 재정의된 요소를 겨냥한다. Axe는 이러한 요소를 음성 입력 사용자가 컨트롤을 활성화하는 것을 직접적으로 막기 때문에 영향 수준 "serious"인 위반으로 보고한다. \n - 자동 탐지의 한계: axe-core와 같은 자동화 도구는 보이는 텍스트가 요소 내부의 일반 텍스트 노드로 렌더링되고, 접근 가능한 이름이 정적인
aria-label또는aria-labelledby속성에서 올 때 불일치를 안정적으로 탐지할 수 있다. 그러나 다음과 같은 경우에는 수동 테스트가 필요하다. (1) 보이는 텍스트가 CSS::before또는::after의사 요소를 통해 렌더링되는 경우 — 브라우저와 보조 기술 버전에 따라 접근성 트리에 포함될 수도, 포함되지 않을 수도 있다. (2) 레이블이 페이지 로드 후 JavaScript로 동적으로 삽입되는 경우. (3) 보이는 텍스트에 아이콘이나 이미지 기반 텍스트가 포함되어 있어 텍스트 구성 요소를 추론해야 하는 경우. (4) 요소의 계산된 접근 가능한 이름이 여러 요소를 이어 붙이는 복잡한aria-labelledby체인을 포함하는 경우. 이러한 상황에서는 스크린 리더를 사용하는 사람이 실제로 무엇이 읽히는지와 화면에 무엇이 보이는지를 비교해 확인해야 한다. \n
테스트 방법
\n- \n
- axe DevTools 또는 Lighthouse를 사용한 자동 스캔: axe DevTools 브라우저 확장(Chrome 또는 Firefox)을 설치하거나 Chrome DevTools에서 Lighthouse 접근성 감사를 실행한다. 완전히 렌더링된 페이지에서 스캔을 실행하되, 인터랙티브 요소를 포함하는 동적 콘텐츠, 모달, 확장된 메뉴가 열려 있는지 확인한다. axe 결과 패널에서 규칙 ID
label-content-name-mismatch로 필터링한다. 표시된 각 요소에는 계산된 접근 가능한 이름과 보이는 텍스트가 함께 표시되어 불일치를 즉시 파악할 수 있다. 요소 선택자를 기록하고 DOM을 검사해 접근 가능한 이름이 어디에서 재정의되었는지 확인한다. Lighthouse는 "Accessibility" 카테고리에서 WCAG 2.5.3을 참조하며 동일한 실패를 표시한다. \n - 브라우저 DevTools를 사용한 수동 검사: 브라우저의 접근성 패널을 연다(Chrome DevTools → Elements → Accessibility 탭, 또는 Firefox DevTools → Accessibility 탭). 보이는 텍스트가 있는 각 인터랙티브 요소를 선택한다. 요소의 "Computed Properties" 섹션에서
Name필드를 확인한다. 이 값을 보이는 레이블과 비교한다. 계산된 이름이 보이는 레이블 텍스트를 부분 문자열로 포함하지 않으면, 해당 요소는 2.5.3을 충족하지 못한다. \n - NVDA + Firefox로 스크린 리더 테스트: NVDA를 실행한 상태에서 Firefox를 연다. Tab 키를 사용해 각 인터랙티브 요소로 이동한다. NVDA가 요소의 이름으로 무엇을 읽는지 듣는다. 읽어 주는 이름과 화면에 표시된 텍스트 사이에 어떤 불일치가 있는지 기록한다. NVDA의 요소 목록(Insert+F7)을 사용해 모든 링크와 버튼을 탐색하고, 한 번에 시각적 레이블과 읽어 주는 이름을 비교한다. \n
- JAWS + Chrome으로 스크린 리더 테스트: JAWS를 실행한 상태에서 Chrome을 연다. 모든 인터랙티브 컨트롤을 Tab 키로 순회한다. JAWS는 접근 가능한 이름 뒤에 역할을 붙여 읽어 준다. 읽어 주는 이름에 보이는 텍스트가 포함되지 않으면 해당 요소를 기록한다. 추가로, JAWS의 "Browse Mode"와 가상 뷰어를 사용해 각 컨트롤의 계산된 접근 가능한 이름을 확인한다. \n
- VoiceOver + Safari(macOS/iOS)로 스크린 리더 테스트: VoiceOver를 활성화한다(macOS에서는 Command+F5). Tab 또는 VO+오른쪽 화살표를 사용해 인터랙티브 요소를 탐색한다. VoiceOver가 각 컨트롤에 대해 읽어 주는 접근 가능한 이름을 듣는다. iOS에서는 한 손가락으로 오른쪽으로 쓸어 요소를 이동하며 이름과 레이블의 불일치를 확인한다. \n
- Voice Control(macOS/iOS) 또는 Dragon으로 음성 인식 테스트: macOS Voice Control을 활성화한다(시스템 설정 → 손쉬운 사용 → Voice Control). "Show names"라고 말해 모든 인터랙티브 요소의 레이블을 표시한다. Voice Control이 표시하는 레이블이 화면의 보이는 텍스트와 일치하는지 확인한다. 보이는 레이블 텍스트를 말해 컨트롤을 활성화해 본다 — 보이는 이름으로 반응하지 않는 컨트롤은 모두 2.5.3 실패이다. 가능하다면 Windows에서 Dragon NaturallySpeaking을 사용해 "Click [label]" 명령으로 동일한 테스트를 반복한다. \n
수정 방법
\n\naria-label이 버튼을 덮어쓰는 경우 — 잘못된 예
\n<!-- FAIL: aria-label이 보이는 텍스트 \"Search\"를 완전히\n \"Execute query\"로 대체해 음성 입력을 깨뜨림 -->\n<button aria-label='Execute query'>\n Search\n</button>\n\naria-label이 버튼을 덮어쓰는 경우 — 올바른 예
\n<!-- PASS: 일치하지 않는 aria-label을 완전히 제거한다.\n 버튼의 보이는 텍스트 \"Search\"가 자동으로 접근 가능한 이름이 된다.\n 음성 사용자는 \"Click Search\"라고 말해 버튼을 활성화할 수 있다. -->\n<button>\n Search\n</button>\n\n<!-- PASS (대안): 추가 문맥이 필요하다면,\n 접근 가능한 이름에 보이는 텍스트가 반드시 포함되도록 한다. -->\n<button aria-label='Search products'>\n Search\n</button>\n\naria-labelledby가 관련 없는 텍스트를 가리키는 링크 — 잘못된 예
\n<!-- FAIL: aria-labelledby가 \"Our Services\"라는 제목을 참조하지만\n 링크는 시각적으로 \"Learn more\"라고 표시되므로,\n 접근 가능한 이름이 \"Learn more\"가 아니라 \"Our Services\"가 됨 -->\n<h2 id='services-heading'>Our Services</h2>\n<p>\n <a href='/services' aria-labelledby='services-heading'>Learn more</a>\n</p>\n\naria-labelledby가 관련 없는 텍스트를 가리키는 링크 — 올바른 예
\n<!-- PASS: aria-labelledby를 사용해 링크 자신의 텍스트와 제목을 이어 붙인다.\n 접근 가능한 이름은 \"Learn more Our Services\"가 되며,\n 보이는 레이블 \"Learn more\"를 포함한다. -->\n<h2 id='services-heading'>Our Services</h2>\n<p>\n <a href='/services' id='learn-link' aria-labelledby='learn-link services-heading'>\n Learn more\n </a>\n</p>\n\n<!-- PASS (대안): 보이는 링크 텍스트를 자체적으로 설명적이게 만들어\n 별도의 재정의가 필요 없도록 한다. -->\n<a href='/services'>Learn more about our services</a>\n\naria-label이 보이는 텍스트와 충돌하는 아이콘 버튼 — 잘못된 예
\n<!-- FAIL: 버튼은 카트 아이콘과 \"Cart\" 텍스트를 모두 보여준다.\n aria-label 'View shopping basket'에는 \"Cart\"가 포함되지 않아\n \"Click Cart\"라고 말하는 음성 사용자는 반응을 얻지 못한다. -->\n<button aria-label='View shopping basket'>\n <svg aria-hidden='true'><!-- cart icon --></svg>\n Cart\n</button>\n\naria-label이 보이는 텍스트와 충돌하는 아이콘 버튼 — 올바른 예
\n<!-- PASS: aria-label에 보이는 텍스트 \"Cart\"가 부분 문자열로 포함된다.\n 음성 사용자는 \"Click Cart\" 또는 \"Click View Cart\"라고 말해도 모두 동작한다. -->\n<button aria-label='View Cart'>\n <svg aria-hidden='true'><!-- cart icon --></svg>\n Cart\n</button>\n\n<!-- PASS (권장): aria-label을 제거하고 아이콘을 트리에서 숨긴다.\n 버튼의 텍스트 콘텐츠 \"Cart\"가 직접 접근 가능한 이름이 된다. -->\n<button>\n <svg aria-hidden='true' focusable='false'><!-- cart icon --></svg>\n Cart\n</button>\n\n보이는 레이블이 있는 폼 입력에 aria-label이 불일치하는 경우 — 잘못된 예
\n\n(Content truncated due to token limit — please retry this article.)
