Tech

카드 UI 웹 접근성

유튜브, 토스, 치지직을 분석하며 배우는 "버튼 안에 버튼" 구현법

들어가며

전체를 링크로 감싸고 내부에 버튼을 넣은 카드 UI 예시

카드 UI를 구현할 때 흔히 전체를 <a> 로 감싸고, 내부에 버튼을 넣어 다양한 인터랙션을 처리하곤 한다. 나 역시 회사 프로젝트에서 유튜브의 카드 UI를 참고해 비슷한 방식을 사용했지만, 이후 접근성 문제를 발견하게 되었고 이를 깊이 탐구하게 되었다.

이번 글에서는 내가 겪은 문제와 해결 과정을 공유하고, 실제 기업들이 같은 문제를 어떻게 풀어내고 있는지도 함께 살펴보겠다.

카드 UI 안의 버튼, 무엇이 문제일까?

<a href='/go-to'>
  <Button>확인했어요.</Button>
</a>
<a href='/go-to'>
  <Button>확인했어요.</Button>
</a>

토스의 접근성 가이드에서 버튼안에 버튼을 넣으면 안된다는 내용을 발견했다.

  1. W3C HTML5 명세에 따르면, <a> 요소는 content model로 transparent(사실상 아무거나)를 포함할 수 있지만, 상호작용 요소를 포함할 수 없다.
  2. 스크린 리더가 잘 못 읽을 수 있다.

토스의 접근성 가이드에서 제시하는 해결책

<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

과거에 영상 카드 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 기본 버튼은 자동으로 키보드 접근성이 보장된다. 탭으로 접근되고, EnterSpace 키로 동작한다.
    • 커스텀 버튼(div, span 등): 기본적으로 접근성이 제공되지 않으므로, role="button", tabindex="0" 속성을 추가하고 keydown 이벤트 핸들링을 직접 구현해야 한다.
  • 잘못된 태그를 사용하면 키보드 탐색 흐름이 깨지고, 사용자의 기대와 실제 동작이 달라져 혼란을 초래할 수 있다.

따라서, "동작만 되면 된다"가 아니라 "동작 + 의미"를 모두 지켜야 한다는 것이 웹 접근성의 핵심이다.

기업들의 사례 분석

메인페이지에서 발견할수 있는 카드 ui내에 버튼이 존재하는 요소를 분석했다.

스크린리더는 VoiceOver로 테스트 하였다.

토스 증권의 예시

토스 증권 메인의 종목 카드

HTML 구조로 구현 유추

  • 전체를 <a> 로 감싸고 하위 종목 버튼은 div로 처리하였다.
  • 하위 종목을 클릭시 개별 페이지로 이동하지만, <button>로 구성하거나 role="button"로 해두지 않아 버튼으로 인식하기 어렵는다.

토스 증권 카드의 HTML 구조를 개발자 도구로 살펴본 화면

키보드 접근성과 스크린 리더

  • <a> 만 인식하므로 카드 전체를 감지한다. 키보드로는 카드 전체에 대한 접근만 가능했다.
  • 링크로 인식하고 전체를 읽어준다.
  • '국내 >' 부분에서, '>'는 상세페이지 이동과 같은 의미인데, 아이콘이 아닌 텍스트로 구현하여 '국내 다음보다 큼'이라고 해석되는 부분은 아쉬웠다.

유튜브의 예시

유튜브 메인의 영상 카드

HTML 구조로 구현 유추

  • 중첩되지 않게 모두 분리하였다.
  • 전체 카드를 클릭했을때 영상페이지로 이동하는 부분은 커스텀 태그(yt-lockup-view-model)를 이용하여 해결한것으로 추측된다.
    • 커스텀 태그를 제거하고 버튼 외 부분을 클릭시 페이지 이동이 되지 않았다.
    • 이벤트 위임을 통해 <a>, <button> 이 아닌 곳 클릭시 영상 페이지로 이동하게끔 한것으로 추측된다.

유튜브 영상 카드의 HTML 구조를 개발자 도구로 살펴본 화면

키보드 접근성과 스크린 리더

  • 썸네일 부분은 tabIndex="-1"로 설정하여 포커싱을 건너뛰는 방식으로 구현하였다.
  • 썸네일 부분은 aria-hidden 속성을 이용하여 읽지 않도록 구현하였다.
    • 썸네일 부분을 건너뛴 이유는 하나의 영상에 대해서 포커싱 요소가 너무 많으면 스크린리더를 사용하는 사용자가 탐색하는데 어려움이 있을것 같아서 제외시킨것 같다.
  • 채널 프로필 이미지의 경우, 채널로 이동한다는 점은 알려주지만 어떤 채널인지 이름은 알 수 없었다. 또한 채널이름 부분은 채널 이름인지 영상 제목인지는 알 수 없었다.

치지직의 예시

치지직 메인의 방송 카드

HTML 구조로 구현 유추

  • 중첩되지 않게 분리했다.
  • 앞선 두 서비스와 비교하여 굉장히 역할에 따른 HTML 사용이 명확한다.
    • 페이지 이동의 경우는 <a> 를 사용하고 버튼의 경우는 <button> 를 사용했다.
  • 다만 카드의 여백을 눌렀을때는 아무런 동작도 수행하지 않는다.

치지직 방송 카드의 HTML 구조를 개발자 도구로 살펴본 화면

키보드 접근성과 스크린 리더

  • 세분화된 모든것에 접근이 가능했다.
  • blind 클래스를 가진 추가 텍스트가 존재해서 현재 읽고 있는것이 채널 이름인지, 제목인지를 구분할수 있게 도와주었다.

기업 사례 비교 및 인사이트

치지직유튜브토스 증권
구현 방법요소를 모두 분리하였고 페이지 이동은 <a>로, 사용자 액션에는 <button>을 사용하였다.요소를 모두 분리하였고 커스텀 태그 (yt-lockup-view-model)를 이용하여 카드 여백 클릭시 동작을 설정했다.<a>안에 <div> 를 위치 시키고 <div>를 클릭시 상세페이지로 이동한다.
특징- 카드의 여백을 눌렀을때는 아무런 동작도 하지 않는다.

- blind 클래스를 이용하여 화면에는 보이지 않지만 스크린리더에게 추가 정보를 제공하여 더 편리했다.
- 영상 부분은 tabIndexaria-hidden을 통해서 건너뛴다.- 세부 종목의 경우는 <div>로 구현했기 때문에, 키보드를 통해 접근이 불가했다.

- ’>’ 를 아이콘이 아닌 텍스트로 구현하여 "다음보다 큼"이라고 읽히는게 아쉬웠다.
  • 토스 가이드의 해결책처럼 절대 위치를 활용하지는 않았지만, 핵심 아이디어인 HTML 구조를 분리하는 방식으로 문제를 해결하고 있었다.
    • 글에는 포함하지 않았지만, 오늘의집의 경우에는 토스의 가이드와 유사하게 절대 위치를 활용해 접근성을 보완하는 방식을 확인했다.
  • 전반적으로 접근성 수준은 치지직 > 유튜브 > 토스증권 순으로 우수했다.

후기

  • 접근성에 관심을 가지게 된 계기는 '시각장애인이 모바일을 어떻게 사용하는지 직접 체험해보세요.'에서 스크린리더를 경험하면서였다.
  • 눈으로는 한 번에 파악할 수 있는 정보도, 스크린리더를 통해서는 요소를 하나씩 순차적으로 읽어야 했기 때문에 같은 정보를 이해하는 데 훨씬 오랜 시간이 걸렸다.
  • 특히 접근성을 고려하지 않은 페이지는 아예 사용이 불가능한 경우도 있었다.
  • 반대로, 단순히 기준을 지키는 수준을 넘어 실제 사용자가 더 편리하게 접근할 수 있도록 구현한 치지직의 사례는 인상 깊었다.

정리하기

  • 핵심은 "동작만 되면 된다"가 아니라, "의미와 동작이 일치해야 한다"는 점이다.
  • 접근성은 특별한 기능을 추가하지 않아도, 시멘틱 HTML을 올바르게 사용하는 것만으로도 상당 부분 보장할 수 있다.
  • <a><button>의 역할을 올바르게 구분하고, 필요한 경우 보조 텍스트(aria, hidden text)를 활용하면 UX를 크게 개선할 수 있다.
  • 결국 "단순히 동작하는 구현"과 "접근성 좋은 구현"은 다르며, "접근성 좋은 구현"이 더 예측 가능하고 편리한 사용자 경험을 제공한다.
이 글이 도움이 되었나요?좋아요는 다음 글을 쓰는 힘이 됩니다

다른 글