웹 접근성 표준을 지키는 "버튼 안에 버튼" 구현법
들어가며
카드 UI를 구현할 때 흔히 전체를 <a>
로 감싸고, 내부에 버튼을 넣어 다양한 인터랙션을 처리하곤 합니다.
저 역시 회사 프로젝트에서 유튜브의 카드 UI를 참고해 비슷한 방식을 사용했지만, 이후 접근성 문제를 발견하게 되었고 이를 깊이 탐구하게 되었습니다.
이번 글에서는 제가 겪은 문제와 해결 과정을 공유하고, 실제 기업들이 같은 문제를 어떻게 풀어내고 있는지도 함께 살펴보겠습니다.
카드 UI 안의 버튼, 무엇이 문제일까?
<a href='/go-to'>
<Button>확인했어요.</Button>
</a>
<a href='/go-to'>
<Button>확인했어요.</Button>
</a>
토스의 접근성 가이드에서 버튼안에 버튼을 넣으면 안된다는 내용을 발견했습니다.
- W3C HTML5 명세에 따르면,
<a>
요소는 content model로 transparent(사실상 아무거나)를 포함할 수 있지만, 상호작용 요소를 포함할 수 없습니다. - 스크린 리더가 잘 못 읽을 수 있습니다.
토스의 접근성 가이드에서 제시하는 해결책
<div
style='position: relative; isolation: isolate;'
className='wrapper'
role='listitem'
aria-label='서비스 검토 관리'
>
<button
className='detail-button'
style='position: absolute; inset: 0; opacity: 0'
>
상세보기
</button>
서비스 검토 관리
<div style='position: relative; z-index: 2;'>
<button aria-label='삭제'>x</button>
</div>
</div>
<div
style='position: relative; isolation: isolate;'
className='wrapper'
role='listitem'
aria-label='서비스 검토 관리'
>
<button
className='detail-button'
style='position: absolute; inset: 0; opacity: 0'
>
상세보기
</button>
서비스 검토 관리
<div style='position: relative; z-index: 2;'>
<button aria-label='삭제'>x</button>
</div>
</div>
- 절대 위치를 이용해서 버튼을 겹쳐 보이게 배치하되, HTML 구조상 중첩되지 않도록 구현합니다.
- 이러한 구조는 두개의 버튼이 시각적으로는 겹쳐있으나 HTML 구조에서는 별도의 위치합니다.
- HTML 표준을 어기지 않으면서 접근성을 준수합니다.
과거 나의 코드의 문제점 분석
과거에 영상 카드 ui를 구현한 적이 있습니다. 유튜브를 참고하며 동일하게 동작하도록 구현했는데, 어떤 문제가 있는지 살펴보겠습니다.
<a>
안에 버튼이 들어가 있습니다.- 심지어 단순한 텍스트(
p
)와 이미지(img
)에onClick
을 붙여 버튼처럼 사용했습니다. - 내부 동작을 막기 위해
e.preventDefault()
를 쓰면서 요구사항은 충족했지만, 접근성 측면에서는 전혀 바람직하지 않은 코드였습니다.
실제로 어떤 문제가 있는 것일까?
버튼안에 버튼을 넣은 저의 코드와 토스의 방법대로 개선한 코드는 모두 동작은 동일했습니다.
접근성 측면에서 어떻게 다른지 실제로 스크린리더를 이용해서 확인해보았습니다.
안드로이드 내장 스크린리더인 TalkBack을 이용하여 테스트하였습니다.
탐색하는 방법
스크린 리더로 탐색하는 방법은 두가지가 있습니다.
- 좌에서 우로 드래그 -> 최상단부터 접근 가능한 요소에 차례대로 접근할 수 있습니다.
- 화면에 특정 요소를 터치 -> 접근 가능한 영역에 포커싱되어 접근할 수 있습니다.
발견한 문제점
<a>
로 감산 1번 카드의 경우는 카드의 세부 요소에 접근할 수 없었습니다.- 즉, 스크린리더는 버튼으로 감싼 카드를 '하나의 버튼 요소'로만 인식하기 때문에, 카드 내부의 세부 정보를 읽을 수 없습니다.
키보드 접근성
- 키보드 접근성은 두개의 카드 모두 정상적으로 버튼 요소에 접근가능했습니다.
결론
결국 HTML 구조를 별도로 분리하는 것이 더 좋은 접근성을 지키는 방법입니다.
접근성 표준과 실제 UX 문제
그럼 접근성 가이드라인에서는 어떻게 구현하도록 정해두었을까요?
웹 접근성 가이드라인(WCAG)과 WAI-ARIA 명세에서는 태그 본연의 의미를 지켜서 사용하는 것을 강조합니다.
<button>
은 '사용자 액션(클릭)'을 위한 요소이고, <a>
는 '페이지 이동'을 위한 요소입니다.
문제는, 개발자가 종종 동작을 맞추기 위해 이 역할을 바꿔치기한다는 겁니다.
잘못된 코드
<button onclick="window.location='/detail'">상세보기</button>
<button onclick="window.location='/detail'">상세보기</button>
- 겉보기에는 잘 작동합니다.
- 그러나 스크린리더는 이 요소를 "버튼"으로 읽습니다.
- 사용자는 버튼을 눌렀는데 페이지가 이동하니 혼란스럽습니다.
키보드 접근성
- W3C Keyboard 명세에 보면 자유로운 움직임을 제외한 모든 작업을 키보드로 할 수 있어야합니다.
<a>
vs<button>
차이<a>
: Tab으로 이동, Enter로 동작<button>
: Tab으로 이동, Enter·Space로 동작
- 접근성 보장 방식
- 네이티브
<button>
: HTML 기본 버튼은 자동으로 키보드 접근성이 보장됩니다. 탭으로 접근되고,Enter
와Space
키로 동작합니다. - 커스텀 버튼(div, span 등): 기본적으로 접근성이 제공되지 않으므로,
role="button"
,tabindex="0"
속성을 추가하고keydown
이벤트 핸들링을 직접 구현해야 합니다.
- 네이티브
- 잘못된 태그를 사용하면 키보드 탐색 흐름이 깨지고, 사용자의 기대와 실제 동작이 달라져 혼란을 초래할 수 있습니다.
따라서, "동작만 되면 된다"가 아니라 "동작 + 의미"를 모두 지켜야 한다는 것이 웹 접근성의 핵심입니다.
기업들의 사례 분석
메인페이지에서 발견할수 있는 카드 ui내에 버튼이 존재하는 요소를 분석했습니다.
스크린리더는 VoiceOver로 테스트 하였습니다.
토스 증권의 예시
HTML 구조로 구현 유추
- 전체를
<a>
로 감싸고 하위 종목 버튼은 div로 처리하였습니다. - 하위 종목을 클릭시 개별 페이지로 이동하지만,
<button>
로 구성하거나role="button"
로 해두지 않아 버튼으로 인식하기 어렵습니다.
키보드 접근성과 스크린 리더
<a>
만 인식하므로 카드 전체를 감지합니다. 키보드로는 카드 전체에 대한 접근만 가능했습니다.- 링크로 인식하고 전체를 읽어줍니다.
- '국내 >' 부분에서, '>'는 상세페이지 이동과 같은 의미인데, 아이콘이 아닌 텍스트로 구현하여 '국내 다음보다 큼'이라고 해석되는 부분은 아쉬웠습니다.
유튜브의 예시
HTML 구조로 구현 유추
- 중첩되지 않게 모두 분리하였습니다.
- 전체 카드를 클릭했을때 영상페이지로 이동하는 부분은 커스텀 태그(
yt-lockup-view-model
)를 이용하여 해결한것으로 추측됩니다.- 커스텀 태그를 제거하고 버튼 외 부분을 클릭시 페이지 이동이 되지 않았습니다.
- 이벤트 위임을 통해
<a>
,<button>
이 아닌 곳 클릭시 영상 페이지로 이동하게끔 한것으로 추측됩니다.
키보드 접근성과 스크린 리더
- 썸네일 부분은
tabIndex="-1"
로 설정하여 포커싱을 건너뛰는 방식으로 구현하였습니다. - 썸네일 부분은
aria-hidden
속성을 이용하여 읽지 않도록 구현하였습니다.- 썸네일 부분을 건너뛴 이유는 하나의 영상에 대해서 포커싱 요소가 너무 많으면 스크린리더를 사용하는 사용자가 탐색하는데 어려움이 있을것 같아서 제외시킨것 같습니다.
- 채널 프로필 이미지의 경우, 채널로 이동한다는 점은 알려주지만 어떤 채널인지 이름은 알 수 없었습니다. 또한 채널이름 부분은 채널 이름인지 영상 제목인지는 알 수 없었습니다.
치지직의 예시
HTML 구조로 구현 유추
- 중첩되지 않게 분리했습니다.
- 앞선 두 서비스와 비교하여 굉장히 역할에 따른 HTML 사용이 명확합니다.
- 페이지 이동의 경우는
<a>
를 사용하고 버튼의 경우는<button>
를 사용했습니다.
- 페이지 이동의 경우는
- 다만 카드의 여백을 눌렀을때는 아무런 동작도 수행하지 않습니다.
키보드 접근성과 스크린 리더
- 세분화된 모든것에 접근이 가능했습니다.
blind
클래스를 가진 추가 텍스트가 존재해서 현재 읽고 있는것이 채널 이름인지, 제목인지를 구분할수 있게 도와주었습니다.
기업 사례 비교 및 인사이트
치지직 | 유튜브 | 토스 증권 | |
---|---|---|---|
구현 방법 | 요소를 모두 분리하였고 페이지 이동은 <a> 로, 사용자 액션에는 <button> 을 사용하였습니다. | 요소를 모두 분리하였고 커스텀 태그 (yt-lockup-view-model)를 이용하여 카드 여백 클릭시 동작을 설정했습니다. | <a> 안에 <div> 를 위치 시키고 <div> 를 클릭시 상세페이지로 이동합니다. |
특징 | - 카드의 여백을 눌렀을때는 아무런 동작도 하지 않습니다. - blind 클래스를 이용하여 화면에는 보이지 않지만 스크린리더에게 추가 정보를 제공하여 더 편리했습니다. | - 영상 부분은 tabIndex 와 aria-hidden 을 통해서 건너뜁니다. | - 세부 종목의 경우는 <div> 로 구현했기 때문에, 키보드를 통해 접근이 불가했습니다. - ’>’ 를 아이콘이 아닌 텍스트로 구현하여 "다음보다 큼"이라고 읽히는게 아쉬웠습니다. |
- 토스 가이드의 해결책처럼 절대 위치를 활용하지는 않았지만, 핵심 아이디어인 HTML 구조를 분리하는 방식으로 문제를 해결하고 있었습니다.
- 글에는 포함하지 않았지만, 오늘의집의 경우에는 토스의 가이드와 유사하게 절대 위치를 활용해 접근성을 보완하는 방식을 확인했습니다.
- 전반적으로 접근성 수준은 치지직 > 유튜브 > 토스증권 순으로 우수했습니다.
후기
- 접근성에 관심을 가지게 된 계기는 '시각장애인이 모바일을 어떻게 사용하는지 직접 체험해보세요.'에서 스크린리더를 경험하면서였습니다.
- 눈으로는 한 번에 파악할 수 있는 정보도, 스크린리더를 통해서는 요소를 하나씩 순차적으로 읽어야 했기 때문에 같은 정보를 이해하는 데 훨씬 오랜 시간이 걸렸습니다.
- 특히 접근성을 고려하지 않은 페이지는 아예 사용이 불가능한 경우도 있었습니다.
- 반대로, 단순히 기준을 지키는 수준을 넘어 실제 사용자가 더 편리하게 접근할 수 있도록 구현한 치지직의 사례는 인상 깊었습니다.
정리하기
- 핵심은 "동작만 되면 된다"가 아니라, "의미와 동작이 일치해야 한다"는 점입니다.
- 접근성은 특별한 기능을 추가하지 않아도, 시멘틱 HTML을 올바르게 사용하는 것만으로도 상당 부분 보장할 수 있습니다.
<a>
와<button>
의 역할을 올바르게 구분하고, 필요한 경우 보조 텍스트(aria
, hidden text)를 활용하면 UX를 크게 개선할 수 있습니다.- 결국 "단순히 동작하는 구현"과 "접근성 좋은 구현"은 다르며, "접근성 좋은 구현"이 더 예측 가능하고 편리한 사용자 경험을 제공합니다.