관리 메뉴

즐겁게, 코드

시각장애인에게 친절한 모달 컴포넌트 만들기 본문

🎨 프론트엔드

시각장애인에게 친절한 모달 컴포넌트 만들기

Chamming2 2024. 12. 27. 14:51

일반인들은 마우스로 원하는 링크나 버튼을 눌러 웹 페이지를 자유롭게 탐색할 수 있지만, 시각장애인들은 주로 키보드의 Tab 또는 Shift + Tab 또는 별도의 장치를 통해 HTML 태그를 계층적으로 탐색합니다.

💡 시각장애인의 웹 탐색 방법이 궁금하다면 스크린 리더 라는 도구에 대해 검색해 보는 것을 추천드립니다.

따라서 프론트엔드 개발자들은 시각장애인들이 일반인과 비슷한 수준의 탐색을 경험할 수 있도록 접근성을 신경쓸 필요가 있는데요, 오늘은 모달(팝업) 컴포넌트를 개발할 때 놓치기 쉬운 접근성을 다뤄 보려 합니다.

 

예시를 위해 간단한 모달을 제작한 모습입니다.

export const Modal = ({ open, handleClose }: Props) => {
  // Backdrop, CloseButton 등의 마크업 코드는 생략하겠습니다.
  
  return open
    ? createPortal(
        <Backdrop>
          <CloseButton onClick={handleClose}>닫기</CloseButton>
          <Body>
            <Header>오늘의 주요 뉴스</Header>
            <p>환율 1470원 돌파, 이대로 괜찮은가?</p>
            <Author>김철수 기자</Author>
            <button>기사 후원하기</button>
          </Body>
        </Backdrop>,
        document.body
      )
    : null;
};

글을 쓰는 중에도 환율이 1480원을 돌파했네요. (2024.12.27)

일반인은 짙은 회색의 Backdrop 영역과 흰 배경을 통해 모달이 열린 것을 알 수 있기 때문에 평범한 모달처럼 보이지만, 시각장애인들은 스크린 리더가 읽어주는 HTML 태그의 속성만으로 이를 구분해야 하기 때문에 팝업이 열리기는 했는지, 이것이 팝업이 맞기는 한지 인식하기 어려워할 가능성이 있습니다.

개선점 1. aria- 접근성 태그 추가하기

가장 기본적인 방법은 스크린 리더가 UI의 힌트를 제공할 수 있도록 접근성 속성을 추가하는 것입니다.

export const Modal = ({ open, handleClose }: Props) => {
 
  return open
    ? createPortal(
        <Backdrop
            // 스크린 리더가 이 모달이 팝업 요소임을 알려줄 수 있게 됩니다.
            role="dialog"
            aria-modal="true"
            // "오늘의 주요 뉴스 팝업"
            aria-labelledby="modal-header"
        >
          <CloseButton onClick={handleClose}>닫기</CloseButton>
          <Body>
            <Header id="modal-header">오늘의 주요 뉴스</Header>
            <p>환율 1470원 돌파, 이대로 괜찮은가?</p>
            <Author>김철수 기자</Author>
            <button>기사 후원하기</button>
          </Body>
        </Backdrop>,
        document.body
      )
    : null;
};

모달 컴포넌트에 role="dialog", aria-modal="true" 속성을 부여하면 스크린 리더는 이것이 평범한 <div><main> 태그가 아닌 모달 UI라는 힌트를 시각장애인에게 전달할 수 있게 됩니다.

크롬 개발자 도구에서 접근성 트리를 확인해 보면 마크업 구조는 수정하지 않았음에도 "닫기" 버튼이 본문과 함께 모달 내에 묶이게 되었고, 현재 탐색하고 있는 UI가 "오늘의 주요 뉴스" 모달임을 명시적으로 안내할 수 있게 되었습니다.

크롬 접근성 트리 : AS-IS / TO-BE

개선점 2. Escape 키로 모달을 닫을 수 있도록 추가하기

앞서 말했듯 시각장애인은 "닫기" 버튼을 곧바로 마우스로 클릭할 수 없고, 키보드 또는 보조 입력 도구를 통해 HTML 계층을 순차적으로 탐색하게 됩니다.

 

따라서 컨텐츠가 많은 팝업에서는 컨텐츠를 조회하다가 팝업을 닫기 위해 뒤로가기 키를 여러 차례 눌러야 하는 불편을 겪을 수 있는데요, 이런 경험을 개선하기 위해 ESC 키를 눌렀을 때 팝업이 닫히는 기능을 추가할 필요가 있습니다.

const Modal = ({ open, handleClose }: Props) => {

  // ESC 키로 모달 닫기
  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape" && handleClose) {
        handleClose();
      }
    };
    if (open) {
      document.addEventListener("keydown", handleKeyDown);
    }
    return () => {
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [open, handleClose]);

  return open
    ? createPortal(
          <Backdrop
            role="dialog"
            aria-modal="true"
            aria-labelledby="modal-header"
          >
            <CloseButton onClick={handleClose}>닫기</CloseButton>
            <Body>
              <Header id="modal-header">오늘의 주요 뉴스</Header>
              <p>환율 1470원 돌파, 이대로 괜찮은가?</p>
              <Author>김철수 기자</Author>
              <button>기사 후원하기</button>
            </Body>
          </Backdrop>,
        document.body
      )
    : null;
};

이처럼 keydown 이벤트에 간단한 핸들러를 추가하는 것으로 시각장애인들이 컨텐츠를 읽다가 Shfit + Tab을 수 차례 눌러 닫기 버튼을 다시 찾아야 하는 불편을 해결할 수 있게 되었습니다.

개선점 3. 포커스 트랩 구현하기

많은 불편이 개선되었지만 아직 한 가지 문제가 남아 있습니다.

시각장애인이 Tab 키를 통해 페이지를 탐색하는 과정에서 모달 바깥의 요소가 focus 동작의 대상이 될 수 있다는 점인데요, 이로 인해 UI상 기존 화면 위에 오버레이된 모달과 그 밑에 깔린 요소들을 분간할 수 없게 되어 탐색에 혼란을 주게 된다는 문제가 있습니다.

나는 모달의 내용을 읽고 싶은데, 모달을 넘어 헤더 영역까지 탐색하게 됩니다.

이를 해결할 수 있는 방법이 바로 focus trap 이라는 기법입니다.

말 그대로 포커스를 가둔다는 의미로 모달이 열려 있는 동안에는 모달 내부의 요소들만 탭을 통해 포커스가 가능하도록 하는 것인데요, 요즘은 이를 직접 구현하지 않아도 focus-trap (바닐라)focus-trap-vue, focus-trap-react 를 통해 간단하게 구현할 수 있습니다.

import { FocusTrap } from "focus-trap-react";

const Modal = ({ open, handleClose }: Props) => {

  useEffect(() => {
    const handleKeyDown = (event: KeyboardEvent) => {
      if (event.key === "Escape" && handleClose) {
        handleClose();
      }
    };
    if (open) {
      document.addEventListener("keydown", handleKeyDown);
    }
    return () => {
      document.removeEventListener("keydown", handleKeyDown);
    };
  }, [open, handleClose]);

  return open
    ? createPortal(
        {/* 모달이 열려있는 동안에는 Focus 가능한 영역을 모달 내부 요소로 한정합니다. */}
        <FocusTrap>
          <Backdrop
            role="dialog"
            aria-modal="true"
            aria-labelledby="modal-header"
          >
            <CloseButton onClick={handleClose}>닫기</CloseButton>
            <Body>
              <Header id="modal-header">오늘의 주요 뉴스</Header>
              <p>환율 1470원 돌파, 이대로 괜찮은가?</p>
              <Author>김철수 기자</Author>
              <button>기사 후원하기</button>
            </Body>
          </Backdrop>
        </FocusTrap>,
        document.body
      )
    : null;
};

이제 시각장애인이 Tab 키를 실수로 잘못 누르더라도 모달 외부의 영역으로 포커스가 이동하지 않아, 사용자가 실수로 탐색 중이던 영역을 벗어나거나 집중을 잃을 가능성을 줄일 수 있게 되었습니다.

탭 키를 여러번 눌러도 모달 내부 요소에만 포커스되는 모습

마치며

위 세 가지 과정을 거쳤다고 해서 시각장애인들을 위한 모든 조치가 이루어진 것은 아니라고 생각합니다.

 

그동안은 저 역시도 비장애인의 위치에서 마크업을 빠르게 작성하는 데에만 초점을 맞춰 오지 않았나 돌아보게 되었는데요, 웹 접근성 인증 마크를 따기 위해서가 아닌 내 서비스를 애용하는 한 명의 시각장애인을 위해서라도 편리한 웹 접근성을 제공하는 습관을 들여 보려 합니다.

반응형
Comments
소소한 팁 : 광고를 눌러주시면, 제가 뮤지컬을 마음껏 보러다닐 수 있어요!
와!! 바로 눌러야겠네요! 😆