드래그 앤 드롭은 사실 편한 UX가 아니다?

황수재 · 토스뱅크 Frontend Developer
2024년 5월 30일

스마트폰에서 드래그 앤 드롭(Drag & Drop) 많이 사용해 보셨을 텐데요. 리스트에 있는 요소의 순서를 변경할 때 드래그 앤 드롭은 많은 사용자에게 편하고 직관적인 UX로 보이죠. 하지만 장애를 가진 사용자에게는 불편한 UX가 되기도 해요.

특히 터치스크린은 정밀한 조작이 필요하기 때문에, 운동장애가 있는 사용자들은 정확한 위치로 요소를 끌어다 놓기 어려워요. 또한 시각 장애가 있는 사용자는 순서가 바뀌었다는 걸 시각적인 피드백만으로는 이해하기 어려울 수 있어요.

토스뱅크에는 용도에 따라 계좌를 생성하여 돈을 나눠서 모을 수 있는 “나눠모으기 통장” 서비스가 있는데요. 서비스를 사용하는 모든 고객이 편하게 계좌의 순서를 바꿀 수 있도록 접근성을 굉장히 많이 신경썼어요. 오늘은 “나눠모으기 통장” 서비스의 접근성 설계 단계부터 개발 과정까지 소개드리고, 접근성을 쉽게 챙길 수 있는 방법을 알려드릴게요.

접근성이 고려된 완성된 형태의 순서 변경 기능

디자이너, 컨설턴트, 개발자간 우당탕탕 커뮤니케이션

토스뱅크에서는 순서 변경이라는 비교적 단순한 기능 하나에도 모든 고객에게 편리한 경험을 선사하고 싶었어요. 그래서 접근성이라는 목표를 향해 디자이너, 접근성 컨설턴트, 개발자 간의 긴밀한 협력이 시작됐어요.

  • 제한적인 화면 크기를 가진 스마트폰에서 깔끔한 UI를 제공하고 싶은 디자이너
  • 장애를 가진 사용자에게도 편리한 UX가 제공되길 원하는 접근성 컨설턴트
  • 이 모든 요구사항을 만족하면서 유지보수가 가능한 코드로 개발하고 싶은 개발자

Step 1: 접근성 이슈의 이해와 설계

이러한 이해관계자들이 모여서 드래그 앤 드롭 순서 변경의 접근성 이슈와, 개선 방식에 대해 심도 있는 논의를 시작했어요. 순서 변경이라는 기능이 간단해 보이지만 접근성 측면에서 고려해야 될 사항이 많았어요.

스크린 리더에서도 드래그 앤 드롭 기능이 동작한다는 사실을 이때 알게 되었는데요. 시각장애인들은 사실상 이 동작을 사용이 어려울 수 있습니다. 그래서 디자이너는 드래그 앤 드롭 조작 방식과 시각장애인도 편하게 쓸 수 있는 탭으로만 조작 방식 두 가지 설계를 시작했습니다.

Step 2: 요구사항 정리

디자이너가 설계한 프로토타입을 기반으로 컨설턴트는 드래그 앤 드롭 순서 변경 설계에서 챙겨야 하는 아래 접근성 포인트들을 알려주셨어요.

  1. 계좌 순서 이동 화면에서 각 계좌 버튼에 aria-pressed 속성을 적용하여 현재 드래그 중인지 아닌지를 true/false로 알림. 그러면 스크린 리더는 선택된 계좌의 토글 버튼을 “꺼짐” 또는 “켜짐”으로 읽어줌. title 속성으로 순서 이동 가능 힌트 추가.
  2. true 상태가 되면(하단에 내리기 올리기 버튼이 표시된 상태가 되면) 초점을 이동하기로 보내기. 이는 해당 버튼을 누른다는 것은 계좌 순서를 변경한다는 뜻이기 때문.
  3. 위로 이동, 아래로 이동으로 버튼에 aria-label 속성 추가.
  4. 위로 이동을 누르거나 아래로 이동을 누르면 “4번째로 이동했어요”, “2번째로 이동했어요” 등의 텍스트가 있는 요소에 aria-live polite 속성 추가함. 그러면 스크린 리더가 텍스트를 읽어줌.
  5. 첫 계좌 또는 마지막 계좌면 위로 또는 아래로 이동 버튼을 disabled 처리. 접근성 초점은 다시 반대쪽 버튼으로 보내기. 그러지 않고 button disabled 속성을 사용하면 초점이 상단으로 튀는 현상이 안드로이드에서 발생할 수 있음.
  6. 마지막으로 완료 버튼을 누르면 해당 계좌로 초점 되돌림.

Step 3: 구현

디자인, 접근성 컨설팅이 끝났으니 이제 제 구현만 남았는데요. 특히 까다로웠던 요구사항 하나만 소개드릴게요. 바로 사용자의 인터렉션 후 초점 이동이에요.

순서를 바꿀 계좌 하나를 선택하면 바텀시트가 뜨면서 ‘올리기’ 또는 ‘내리기’ 버튼에 초점을 주어야 했어요. 아래 그림의 왼쪽 화면과 같이 바텀시트가 뜨는 타이밍에 맞추어 ‘올리기’ 또는 ‘내리기’ 버튼에 focus API를 통해 초점을 이동시켜주었습니다.

순서 이동이 완료되면, 아래 그림의 오른쪽 화면과 같이 바텀시트가 사라지면서 순서 이동을 한 계좌에 다시 초점을 되돌려야 했는데요. 순서 이동을 선택한 계좌를 저장해두고, 바텀시트가 사라지는 타이밍에 다시 focus 를 잡아주었습니다.

그리고 순서가 바뀌었을 때는 aria-live=”polite” 속성에 동적으로 변경된 순서를 스크린 리더에 읽어주도록 구현했습니다.

<div ref={ariaLiveRef} aria-live="polite" style={{ height: 0, overflow: 'hidden' }} />

// 위에 생성해둔 dom을 조작하는 비지니스 로직
const ariaLiveRef = useRef<HTMLDivElement>(null);
// NOTE: 스크린리더에게 메시지를 전달하기 위한 함수
const readA11yMessage = (message: string) => {
  if (ariaLiveRef.current) {
    ariaLiveRef.current.setAttribute('aria-hidden', 'false');
    ariaLiveRef.current.innerText = message;
    setTimeout(() => {
      if (ariaLiveRef.current) {
        ariaLiveRef.current.setAttribute('aria-hidden', 'true');
      }
    }, 500);
  }
};

디바이스별로 다 다른 접근성

다양한 웹 브라우저에서 일관된 동작과 UI를 제공하는 것을 크로스 브라우저 대응이라고 하는데요. 크로스 브라우저 대응 개발을 해보신 분들은 이게 얼마나 어렵고 시간이 많이 소요되는 일인지 아실 거예요. 접근성 또한 크로스 브라우저, os 버전 대응이 필요했어요. 서비스 사용자들에게 일관성 있는 접근성을 제공하기 위해 다양한 디바이스에서 검증하고 수정하는 작업도 같이 수행했습니다.

예를 들어 아이폰에서 listrole listitemtitle 속성을 넣어도 스크린 리더가 텍스트를 제대로 읽지 않는 문제가 있었어요. 또 aria-roledescription는 aria 버전 1.3부터 지원하는 걸 알게 됐는데요. 아이폰은 버전 1.3을 지원하지 않았습니다. 그래서 이런 문제들은 아래와 같이 대응했어요.

role="radio"
title="순서 이동 팝업 열기"
aria-checked=true/false

그리고 안드로이드에서는 onClick을 비동기로 사용하면 checkbox를 클릭할 때 상태를 두 번 읽어주는 현상을 발견했어요. 테스트하며 발견한 접근성 관련 버그는 최소 재현 가능 범위로 구현해서 구글에 이슈를 등록하기도 했습니다.

// 접근성 대응하며 발견한 안드로이드 이슈
// onClick이 async 하면 checkbox를 클릭할때 상태를 두번 읽어주는 현상
<div
  onClick={async () => {
    await delay(1000);
    setChecked(!checked);
  }}
  aria-checked={undefined}
>
  <input type="checkbox" checked={checked} />
</div>

모두를 위한 간단한 접근성 챙겨보기

토스뱅크는 장애를 가진 고객에게 접근성을 제공하는 것을 매우 중요하게 생각하고 있어요. 하지만 “나눠모으기 통장” 서비스와 같이 꼼꼼하게 접근성을 챙기는 것은 어려운 일이죠. 그래서 자주 사용되고 비교적 간단한 접근성 개발 방법을 공유드릴게요.

1. 언어(Language): lang

문서나 요소의 언어를 명시합니다. 가장 간단하게 챙길 수 있는 접근성 요소입니다.

<html lang="ko">

2. 역할(Role): role

요소의 역할을 명시합니다. 클릭이 가능한 영역은 가능하면 div가 아닌 button tag를 사용하시는 것을 추천하지만 상황이 여의치 않은 경우 role을 추가해주세요.

<div role="button">클릭 가능 영역</div>

3. 대체 텍스트(Alternative Text): alt

이미지에 대한 대체 설명을 제공하는 속성입니다.

<img src="cat.jpg" alt="귀여운 고양이 사진">

4. 폼 레이블(Label for Form Elements): labelfor

웹 서비스는 다양한 폼을 사용하게 되는데 이때 폼 요소에 레이블을 제공합니다.

<label for="product-name">상품명:</label>
<input type="text" id="product-name" name="product-name"

5. 폼 그룹(Form Grouping): fieldsetlegend

관련된 폼 요소들을 그룹화하고 설명을 제공합니다.

<fieldset>
  <legend>상품 정보</legend>
  <label for="product-name">상품명:</label>
  <input type="text" id="product-name" name="product-name">
  <br>
  <label for="product-description">상품설명:</label>
  <input type="text" id="product-description" name="product-description">
</fieldset>

6. 탭 순서(Tab Order): tabindex

요소의 탭 순서를 제어합니다. tabindex를 필요한 곳에 적절하게 적용하여 스크린 리더 사용자가 화면을 탐색하는 것에 도움을 줍니다.

  • tabindex="0": 자연스러운 탭 순서에 포함.
  • tabindex="-1": 포커스 가능하지만 탭 순서에는 포함되지 않음.
<button tabindex="0">버튼</button>
<div tabindex="-1">div</div>

7. ARIA (Accessible Rich Internet Applications):그외 다양한 aria- 속성들

ARIA는 접근성을 향상시키기 위해 다양한 속성을 제공합니다.

  • aria-label: 요소에 레이블을 추가합니다. 특히 아래 예시 코드와 같이 “X”로 표시된 닫기 버튼에 활용하면 스크린리더 사용자에게 도움돼요.
    <button aria-label="닫기">X</button>
  • aria-labelledby: 다른 요소의 ID를 참조하여 레이블을 제공합니다.
    <div id="title">폼 제목</div>
    <form aria-labelledby="title"
    
    
  • aria-describedby: 다른 요소의 ID를 참조하여 설명을 제공합니다.
    <input type="text" aria-describedby="description">
    <div id="description">이름을 입력해주세요</div>
  • aria-hidden: 요소를 스크린 리더에서 숨깁니다.
    <div aria-hidden="true">스크린 리더에서 보이지 않는 요소입니다</div>

참고로, aria 속성들 중에서는 구형 브라우저에서 동작하지 않는 경우도 있어요. 예를 들어, aria-roledescription은 aria 1.3 스펙을 지원하는 최신 브라우저에서만 동작합니다.

말씀드린 7 가지 속성 외에도 챙길 수 있는 부분이 많지만, 이것만이라도 적절하게 활용하면 웹 콘텐츠의 접근성을 크게 향상시킬 수 있습니다. 또한 토스와 같이 사내에서 디자인 시스템을 활용하고 있는 경우라면, 디자인 시스템에 이러한 속성들을 적용함으로써 전제 서비스의 접근성을 개선할 수 있어요.

오늘은 토스뱅크에서 접근성을 다양한 방면에서 챙긴 사례를 알려드렸는데요. 지금 개발하고 있는 서비스에서도 적용할 수 있는 팁을 얻었길 바랍니다. 토스뱅크도 모든 고객이 편리하게 금융 서비스를 사용할 수 있도록 계속 노력하겠습니다.

댓글 0댓글 관련 문의: toss-tech@toss.im
연관 콘텐츠
㈜비바리퍼블리카 Copyright © Viva Republica, Inc. All Rights Reserved.