ARIA 역할 버튼: Guía Completa y Páctica
ARIA role="button"은 보조 공학 기기가 일반 컨테이너를 버튼으로 안내하게 만드는 ARIA 역할이지만, 동작(behavior)을 제공하지는 않습니다. 따라서 tabindex="0"을 추가하고, Enter 및 Space 키 입력을 처리하며, aria-pressed 또는 aria-disabled로 상태를 반영해야 합니다. WAI-ARIA 1.2 명세는 이 역할을 위젯(widget) 카테고리로 정의하며, ARIA의 제1원칙은 가능한 한 항상 네이티브 <button>을 사용할 것을 권장합니다.
ARIA 역할 button은 웹 인터페이스의 접근 가능한 시맨틱을 정의하는 W3C 표준인 WAI-ARIA 역할 분류 체계에 속합니다. 요소에 role="button"이 부여되면, 스크린 리더(NVDA, JAWS, VoiceOver, Narrator)가 구축하는 접근성 트리(accessibility tree)는 해당 요소를 일반적인 div나 span이 아닌 실행 가능한 컨트롤로 노출합니다. 이는 스크린 리더 사용자와의 상호작용에서 매우 큰 차이를 만듭니다. 역할이 없으면 사용자는 “그룹” 또는 단순한 텍스트로 듣게 되지만, 역할이 있으면 “버튼”으로 듣게 되어 이를 활성화할 수 있음을 알게 됩니다.
흔히 하는 실수는 role="button"이 div를 기능적인 버튼으로 변환한다고 믿는 것입니다. 그렇지 않습니다. 역할은 안내되는 시맨틱만 변경할 뿐이며, 포커스 받기, 키보드 응답, 액션 실행과 같은 동작은 여전히 개발자의 책임입니다. 시맨틱과 동작의 이러한 분리가 이 역할을 사용할 때 발생하는 대부분의 오류의 근본 원인입니다.
WAI-ARIA는 W3C 권고안으로 발행되었으며, 버전 1.2는 역할, 상태 및 속성에 대한 현재 기준입니다. button 역할은 ARIA 1.0부터 존재했던 명세 내에서 가장 오래되고 안정적인 위젯 역할 중 하나입니다.
role=“button”을 사용해야 할 때와 사용하지 말아야 할 때
WAI-ARIA 저작 실무(WAI-ARIA Authoring Practices)에 수록된 ARIA의 제1원칙은 단호합니다. 필요한 시맨틱과 동작을 가진 네이티브 HTML 요소가 있다면 그것을 사용하십시오. <button>은 이미 암시적 역할, 키보드 포커스, Enter 및 Space 활성화, 비활성화 상태를 모두 갖추고 있습니다. 이 모든 것을 role="button"으로 다시 구현하는 것은 추가 작업일 뿐만 아니라 버그의 온상이 됩니다.
그럼에도 불구하고 이 역할이 정당하게 필요한 시나리오가 있습니다. 가장 흔한 경우는 프레임워크나 CMS로 인해 마크업이 제한되어, 기존 레이아웃이나 JavaScript를 깨뜨리지 않고 <button>을 도입할 수 없을 때입니다. 또 다른 경우는 버튼처럼 동작해야 하지만 내부 구조상 특정 컨테이너가 필요한 컴포넌트(예: 일부 서드파티 위젯)의 경우입니다.
관련 항목: — 48시간 동안 WCAG에 대한 IA의 중첩.
세 번째 시나리오는 이미 다른 네이티브 역할을 가지고 있지만 버튼으로 표시되어야 하는 요소의 경우인데, 이는 논란의 여지가 있습니다. 이때는 디자인이 잘못된 시맨틱을 강요하고 있는 것은 아닌지 자문해 보아야 합니다. 버튼처럼 보이지만 실제로는 다른 페이지로 이동한다면, 버튼이 아니라 링크 <a href>를 사용하는 것이 옳습니다.
| 상황 | 권장 솔루션 | 이유 |
|---|---|---|
| 폼 또는 인터페이스 내 액션 | 네이티브 <button> | 역할, 포커스, 키보드 지원 포함 |
| 다른 URL로 이동 | <a href> | 올바른 링크 시맨틱 |
| 수정 불가능한 컨테이너 | role="button" + 키보드 + 상태 | 역할이 유용한 유일한 경우 |
| 토글 버튼 (on/off) | <button aria-pressed> | 상태가 네이티브하게 노출됨 |
| 비활성화된 버튼 | <button disabled> | 상태 및 포커스 관리됨 |
role=“button”을 올바르게 구현하는 방법
role="button" 기반의 버튼은 포커스, 키보드, 상태, 접근 가능한 이름이라는 네 가지 측면을 모두 충족해야 합니다. 이 중 하나라도 누락되면 겉으로는 접근 가능해 보이지만 실제 테스트에서는 실패하는 컨트롤이 됩니다.
포커스는 tabindex="0"으로 활성화하며, 이를 통해 요소를 자연스러운 탭 순서에 삽입합니다. tabindex에 양수 값을 절대 사용하지 마십시오. 이는 페이지 전체의 탭 순서를 변경하여 예측 불가능한 점프를 유발합니다. 사용자 정의 대화형 요소에는 0 값이 적절합니다.
볼만한 가치가 있는 곳: — Accesibilidad gestionada: 자동화와 인간의 개정 결합.
키보드 처리는 Enter와 Space 두 키를 다뤄야 합니다. 네이티브 <button>은 두 키 모두에 응답하지만, 버튼 역할을 가진 div는 스스로 그렇게 하지 않습니다. keydown 이벤트를 리스닝하여 키가 Enter 또는 Space일 때 액션을 실행해야 하며, 특히 Space 키가 기본적으로 유발하는 페이지 스크롤을 preventDefault()로 막아야 합니다. 이 세부 사항은 자주 간과되어 마우스로만 작동하는 버튼을 만드는 원인이 됩니다.
상태는 ARIA 속성으로 전달합니다. 토글 버튼은 aria-pressed="true" 또는 "false"를 사용하여 활성 상태를 나타냅니다. 비활성화된 버튼은 aria-disabled="true"를 사용하는데, 이는 상태를 안내할 뿐 상호작용을 자동으로 막지는 않으므로 코드에서 액션을 차단해야 합니다. aria-disabled와 네이티브 disabled 속성의 차이는 중요합니다. 전자는 요소를 포커스 가능하게 유지하지만, 후자는 탭 순서에서 제외시키기 때문입니다.
접근 가능한 이름은 요소의 가시 텍스트로 구축하거나, 텍스트가 없는 경우 aria-label 또는 aria-labelledby를 사용합니다. 접근 가능한 이름이 없는 버튼은 스크린 리더가 무엇을 하는지 힌트 없이 단순히 “버튼”이라고만 안내하게 됩니다.
<div
role="button"
tabindex="0"
aria-pressed="false"
id="btn-modo"
>
다크 모드
</div>
<script>
const btn = document.getElementById('btn-modo');
function toggle() {
const activo = btn.getAttribute('aria-pressed') === 'true';
btn.setAttribute('aria-pressed', String(!activo));
}
btn.addEventListener('click', toggle);
btn.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
toggle();
}
});
</script>
위 예제는 포커스, 키보드, 상태, 이름을 모두 다룹니다. 이는 컨트롤이 스크린 리더와 키보드로 사용 가능하기 위한 최소한의 구현입니다.
흔한 오류 및 감지 방법
가장 흔한 오류는 tabindex 없이 role="button"만 적용하는 것으로, 이는 키보드로 접근할 수 없는 버튼을 만듭니다. 자동 도구는 이를 “포커스 가능하지 않은 대화형 역할 요소”로 감지하며, 이는 접근성 감사에서 가장 많이 보고되는 위반 사항 중 하나입니다.
두 번째 오류는 키보드 처리를 하지 않는 것입니다. role="button"과 tabindex는 있지만 키 핸들러가 없는 div는 포커스는 받지만 Enter나 Space에 응답하지 않습니다. 키보드 사용자는 버튼에 포커스는 맞출 수 있지만 활성화할 수 없는 상태에 빠지게 됩니다.
관련 항목: — La certificación profesional que acredita tu experiencia en accesibilidad.
세 번째 오류는 링크에 role="button"을 사용하는 것입니다. 이는 내비게이션 시맨틱을 깨뜨리고, 사용자가 링크를 기대하는 상황에서 스크린 리더가 “버튼”이라고 안내하여 혼란을 줍니다. 요소가 페이지를 이동시킨다면 링크여야 합니다.
네 번째 오류는 토글 버튼에서 상태를 잊는 것입니다. aria-pressed 없이 두 모드 사이를 전환하는 버튼은 사용자가 현재 어떤 상태인지 알 수 없게 만듭니다. 이 속성은 상태를 프로그램 방식으로 전달하는 유일한 방법입니다.
이러한 문제를 감지하기 위해 axe, Lighthouse, WAVE와 같은 자동 도구는 역할 및 포커스 위반을 지적하지만, 키보드 동작이나 상태 로직까지 검증하지는 않습니다. 이 부분은 수동 테스트가 필요합니다. Tab으로 이동하고, Enter와 Space로 활성화하며, 스크린 리더의 출력을 직접 들어보아야 합니다. 자동 감사와 수동 테스트의 조합만이 사용자 정의 버튼을 검증하는 유일하고 신뢰할 수 있는 방법입니다.
role=“button” 버튼 테스트 방법
테스트는 키보드부터 시작합니다. Tab 키로 페이지를 이동하며 role="button" 버튼이 가시적인 포커스를 받는지 확인합니다. Enter와 Space를 각각 눌러보십시오. 둘 다 액션을 실행해야 합니다. Space를 눌렀을 때 버튼이 활성화되는 대신 페이지가 스크롤된다면 preventDefault가 누락된 것입니다.
두 번째는 스크린 리더 테스트입니다. Windows의 NVDA, macOS의 VoiceOver, Windows의 Narrator가 가장 많이 사용됩니다. 버튼에 포커스가 갔을 때, 리더가 역할(“버튼”), 접근 가능한 이름, 그리고 상태(있는 경우)를 안내해야 합니다. “그룹”이라고 안내하거나 상태를 언급하지 않는다면 문제가 있는 것입니다.
세 번째는 포커스 순서 테스트입니다. 버튼은 시각적 위치에 맞게 논리적인 탭 순서에 나타나야 합니다. 양수 tabindex는 이 순서를 깨뜨리며, 이는 잘못된 구현의 명확한 지표입니다.
네 번째는 대비 및 포커스 표시 테스트입니다. 포커스 표시자는 가시적인 대안 없이 outline: none으로 제거해서는 안 됩니다. 포커스는 받지만 표시되지 않는 버튼은 키보드 사용자에게 현재 위치에 대한 기준을 제공하지 못합니다.
button 역할의 현대적 대안
네이티브 <button> 요소는 2024년 현재에도, 그리고 모든 새로운 프로젝트에서도 여전히 최선의 선택입니다. 추가 코드 없이 역할, 포커스, 키보드, 상태를 제공하며 브라우저들이 일관되게 처리합니다.
커스텀 엘리먼트(Custom Elements)나 웹 컴포넌트(Web Components)라는 또 다른 길이 있습니다. HTMLElement를 확장하는 컴포넌트는 버튼 동작을 캡슐화하고 올바른 시맨틱으로 노출할 수 있지만, role="button"과 마찬가지로 포커스와 키보드를 수동으로 관리해야 합니다. 장점은 재사용성이고, 단점은 구현 책임이 동일하다는 점입니다.
ARIA in HTML(각 HTML 요소에 어떤 ARIA 역할이 허용되는지 정의하는 규칙)은 네이티브 역할을 덮어쓰는 것을 권장하지 않습니다. <button>에 role="button"을 적용하는 것은 중복이며, href가 있는 <a>에 적용하는 것은 역효과를 냅니다. 일반적인 권장 사항은 자체 시맨틱이 없는 컨테이너에만 이 역할을 예약하는 것입니다.
Key Takeaways
role="button"은 안내되는 시맨틱만 변경합니다. 포커스, 키보드, 상태는 수동으로 구현해야 합니다.- ARIA의 제1원칙은 마크업이 허용하는 한 항상 네이티브
<button>을 사용할 것을 권장합니다. role="button"버튼에는tabindex="0", Enter 및 Space 처리, 접근 가능한 이름, 그리고aria-pressed또는aria-disabled상태가 필요합니다.- 자동 도구는 포커스 누락을 감지하지만, 키보드 동작과 상태는 스크린 리더를 통한 수동 테스트가 필수적입니다.
- 절대 양수
tabindex를 사용하지 말고, 페이지를 이동하는 링크에role="button"을 적용하지 마십시오.
자주 묻는 질문 (FAQ)
role=“button”과 button 요소의 차이점은 무엇인가요?
네이티브 <button> 요소는 추가 코드 없이 암시적 역할, 키보드 포커스, Enter 및 Space 활성화, 비활성화 상태를 모두 포함합니다. 반면 role="button" 속성은 안내되는 시맨틱만 제공하며, 나머지 동작은 모두 프로그래밍해야 합니다. 이것이 ARIA의 제1원칙이 가능한 한 네이티브 요소를 권장하는 이유입니다.
role=“button”에 tabindex가 필요한가요?
네. div나 span에 role="button"을 부여해도 기본적으로 포커스 가능하지 않습니다. 따라서 tabindex="0"이 없으면 탭 순서에서 제외되어 키보드로 접근할 수 없습니다. 올바른 값은 0이며, 양수 값은 페이지 전체의 탭 순서를 변경하므로 피해야 합니다.
role=“button”을 가진 div가 Enter와 Space에 응답하게 하려면 어떻게 하나요?
keydown 이벤트를 리스닝하여 키가 Enter 또는 Space일 때 액션을 실행하도록 구현해야 합니다. Space 키의 경우 페이지 스크롤을 방지하기 위해 preventDefault()를 호출하는 것이 좋습니다. 이러한 핸들러가 없으면 버튼이 포커스는 받지만 키보드로는 활성화되지 않습니다.
버튼이 활성화되었거나 비활성화되었음을 어떻게 나타내나요?
토글 버튼의 경우 상태에 따라 aria-pressed="true" 또는 "false"를 사용합니다. 비활성화된 버튼의 경우 aria-disabled="true"를 사용하는데, 이는 상태를 안내할 뿐 상호작용을 자동으로 막지는 않으므로 코드에서 액션을 차단해야 합니다. <button>을 사용할 때는 네이티브 disabled 속성이 더 선호됩니다.
링크에 role=“button”을 사용하는 것이 나쁜 관습인가요?
네, 링크가 다른 URL로 이동하는 경우라면 그렇습니다. <a href>의 역할을 role="button"으로 덮어쓰면 내비게이션 시맨틱이 깨지고, 사용자가 링크를 기대하는 상황에서 스크린 리더가 “버튼”이라고 안내하여 혼란을 줍니다. 요소가 페이지를 이동시킨다면 링크 역할을 유지해야 합니다.
role=“button”의 오류를 감지하는 도구는 무엇인가요?
axe, Lighthouse, WAVE와 같은 자동 도구는 대화형 역할 요소의 포커스 누락과 같은 위반 사항을 지적합니다. 하지만 키보드 동작이나 상태 로직은 검증하지 않으므로, NVDA, VoiceOver, Narrator를 이용한 수동 테스트와 자동 감사를 병행하는 것이 가장 신뢰할 수 있는 검증 방법입니다.
자주 묻는 질문
¿ Cuál es la diferencia entre role='button' 및 요소 버튼이 있습니까?
<버튼> 요소는 기본적으로 rol implícito, el foco por teclado, la activación con Enter y Espacio y el estado deshabilitado sin código adicional을 포함합니다. 역할 속성='버튼' 단독으로 의미를 알 수 있음; el Resto del comportamiento debe programarse. Por eso la Primera regla de ARIA recomienda el elemento nativo siempre que sea posible.
¿ 역할 = '버튼'에 tabindex가 필요합니까?
시. div의 범위에 관계없이 역할='버튼'을 적용할 수 없으면 tabindex='0'으로 인해 테이블 명령을 취소할 수 없으며 기술에 사용할 수 없습니다. El Valor Correcto es 0; Los Valores positivos alteran el orden de tabulación de toda la página y deben evitarse.
¿ Cómo hago que un div con role='button' responsea a Enter y Espacio?
Hay que escuchar el evento keydown y ejecutar la acción cano la tecla es Enter o Space. En el caso de Espacio conviene llamar a PreventDefault para evitar el desplazamiento de página. Sin estos manejadores, el botón recibe foco pero no se activa con teclado.
¿Cómo indico que un botón está activo or deshabilitado?
aria-pressed='true' 또는 'false'로 변경된 경우 미국의 대체 버튼을 선택하세요. Para un botón deshabilitado usa aria-disabled='true', que anuncia el estado pero no bloquea la interacción por sí solo: hay que impedir la acción en el código. 기본적으로 비활성화된 경우 <버튼>을 사용하는 것이 좋습니다.
¿ Es Mala práctica usar role='button' en enlace?
그렇다면, 다른 URL을 알려주십시오. <a href> 역할에 대해 설명하면 navegación의 의미를 나타내는 '버튼'을 사용하여 판탈라의 강사를 혼동할 수 있으며, 'botón'은 사용자에게 유용한 정보를 제공할 수 있습니다. Si el elemento navega, debe conservar su rol de enlace.
¿ Qué herramientas는 역할 = '버튼'에서 오류를 감지합니까?
자동 도끼, 등대 또는 WAVE를 사용하여 상호 작용 요소를 제어할 수 있는 도구가 자동으로 제공됩니다. 금지된 상태에서는 NVDA, VoiceOver 또는 내레이터를 사용하여 수동으로 실행 가능한 검증 가능한 조합 감사관 자동 기능을 사용할 수 있는 검증된 기술이 없습니다.
Añade는 5분 만에 도착합니다.
무료로 계획을 세우는 위젯 de empezar hoy mismo