ARIA 적용하여 웹 접근성 향상하기

Ji-Heon Park·2024년 8월 14일
3

TmaxRG

목록 보기
6/10

0. 서론

웹 접근성이란 장애를 가진 사용자들을 포함해 모든 사용자가 웹 콘텐츠에 동등하게 접근하고 이용할 수 있도록 웹을 설계 및 개발하는 것을 의미합니다. 현재 교육 분야에서 프론트엔드 개발을 담당하고 있는 만큼, 웹 접근성은 모든 사용자에게 정보를 공평하게 제공하기 위해 반드시 고려해야 할 중요한 요소라고 생각합니다.

최근 프로젝트에서 일반 사용자뿐 아니라 모두에게 동등한 경험을 제공하기 위해 다양한 상호작용 시나리오를 고려한 ARIA 적용 사례를 공유하려 합니다.

개인적으로 Youtube-스크린 리더 사용 예시를 참고하면 스크린 리더 사용자들이 직면할 수 있는 시나리오를 이해하는 데 도움이 되었습니다.

1. ARIA의 사용 이유

기본적으로, 시맨틱 태그를 사용해 상황에 맞는 적절한 태그를 사용하면 접근성이 크게 떨어지지 않습니다.

<!-- 잘못된 예시 -->
<img src='' alt='삭제' onClick={handleDelete}/>

<!-- 올바른 예시 -->
<button onClick={handleDelete}>삭제</button>

하지만 모든 상황에서 시맨틱 태그를 사용하는 것이 현실적으로 어려운 경우가 있습니다.

  • 모달의 바깥 부분을 클릭하면 모달이 닫히는 이벤트
  • 아이콘 클릭, 카드, 섹션 클릭 등

위와 같이 클릭 가능한 요소가 시맨틱 태그로 구현되지 않는 경우가 생길 수 있습니다. 이런 경우, 스크린 리더 사용자에게 웹 페이지의 구조를 이해하는 데 어려움을 줄 수 있습니다.

이때 ARIA(Accessible Rich Internet Applications) 속성을 사용하면, 스크린 리더 사용자에게 추가 정보를 제공하여 문제를 해결할 수 있습니다.

ARIA 란?

ARIA는 W3C가 규정한 기술 사양으로, 웹 페이지의 접근성을 높이는 방법에 대해 규정하고 있습니다.

ARIA 속성은 필요에 따라 사용해야 하며, 모든 태그에 적용할 필요는 없습니다. 사용자가 상호작용이 가능한 요소, 스크린 리더 사용자에게 중요한 정보를 제공해야 하는 경우에 적절히 사용함으로써, 웹 접근성을 효율적으로 향상시킬 수 있습니다.

2. 사용 예시

ARIA 속성은 다양한 종류가 있지만 사용해보면서 유용하다고 생각한 몇가지 예시를 들겠습니다.

  • role: 요소의 역할을 명시적으로 정의합니다.
  • aria-label: 요소에 대한 접근성을 위해 시각적으로 보이지 않는 텍스트 레이블을 제공합니다.
  • aria-live: 동적으로 업데이트되는 콘텐츠를 스크린 리더가 실시간으로 읽도록 설정합니다.
  • aria-modal: 모달 창이 활성화될 때, 이 속성을 true로 설정하면 스크린 리더는 모달 내부에만 포커스를 두고 모달 외부의 콘텐츠는 무시하게 됩니다.
  • aria-labelledby: 다른 요소의 id를 참조하여 해당 요소가 현재 요소의 레이블 역할을 하도록 지정합니다.
  • aria-describedby: 현재 요소에 대한 추가 설명을 제공하는 요소의 id를 참조합니다.
  • 텍스트를 작성할 때는 서비스가 운영되는 지역(국가)에 맞추어 적절한 언어를 사용하면 됩니다.
  • 사진은 개발중인 서비스 사진이 아닌 예시 사진 입니다. 시각적 참고만 부탁드립니다.

2.1. Icon Button

aria-label

<IconButton onClick={handleDelete} aria-label="삭제">
  <DeleteIcon
    src={DeleteFileIcon}
    alt="delete-icon"
  />
</IconButton>

위 코드는 텍스트 없이 아이콘(<img/>)만 있는 버튼입니다.

aria-label="삭제"는 시각적으로는 "삭제"라는 텍스트가 보이지 않지만, 스크린 리더는 이 속성을 통해 이 버튼의 기능이 "삭제"라는 것을 사용자에게 읽어줍니다. (label이 없는 input에도 유용합니다!)

2.2. Toast

role, aria-live

const ToastsObserver = () => {
  const toasts = useToast();

  if (!toasts || toasts.length < 1) {
    return null;
  }

  return createPortal(
    <ToastContainer>
      {toasts.map((toast) => (
        <Toast
          key={toast.id}
          type={toast.type}
          role="alert"
          aria-live="assertive"
        >
          {toast.message}
        </Toast>
      ))}
    </ToastContainer>,
    document.body
  );
};

위 코드는 에러가 발생했을 때 토스트 창을 통해 사용자에게 알림 UI를 렌더링하는 예제입니다.

role="alert"을 사용하여 스크린 리더에게 이 요소가 중요한 알림임을 알립니다. 알림은 즉각적인 주의가 필요하므로 스크린 리더는 이 요소를 사용자에게 우선적으로 읽어줍니다.

aria-live="assertive" 속성을 통해 스크린 리더는 이 콘텐츠가 화면에 나타나자마자 즉시 읽도록 설정됩니다. 서버 에러와 같은 중요한 정보를 사용자에게 즉시 전달하는 데 적합합니다.

(aria-live="polite"는 사용자가 인터랙션을 마칠 때 업데이트된 내용을 읽어줍니다.)

2.3. Dialog

role, aria-label,aria-modal, aria-labelledby, aria-describedby

const Dialog = ({ children, open, onClose }: DialogProps) => {
  if (!open) return null;

  const onClickOverlay = () => {
    if (typeof onClose === 'function') {
      onClose();
    }
  };

  return (
    <Overlay
      onClick={onClickOverlay}
      role={onClose && 'button'} // 조건 처리
      aria-label={onClose && '모달 바깥 영역 클릭하여 모달 닫기'}
      aria-modal="true"
    >
      <DialogBody
        onClick={(e) => e.stopPropagation()}
        role="alertdialog"
        aria-labelledby="dialog-title" // id가 dialog-title인 요소가 레이블 역할
        aria-describedby="dialog-desc"
      >
        {children}
      </DialogBody>
    </Overlay>
  );
};

모달창은 토스트 창과 다르게 기존의 창을 조작할 수 없습니다. 그렇기에 aria-modal="true"를 주어 모달 외부의 콘텐츠는 무시하게 합니다.

또한 Optional인 onClose 콜백함수는 undefined가 아닐 때, 모달 외부를 클릭할시 모달이 닫히도록 구현되어 있습니다. 조건문에 맞춰 rolearia-label도 onClose 콜백함수가 있을때만 등록할 수 있습니다. (role="button" 속성을 통해 클릭 가능한 영역임을 알립니다.)

function DialogHeader({
  children,
  ...others
}: PropsWithChildren<HTMLAttributes<HTMLDivElement>>) {
  return (
    <Header id="dialog-title" {...others}> // 이 요소를 레이블 요소로 활용
      {children}
    </Header>
  );
}

aria-labelledby="dialog-title"모달 헤더의 id와 연결되어 이 요소를 다이얼로그의 레이블로 사용합니다. 스크린 리더는 이 id로 참조된 텍스트를 다이얼로그의 제목으로 읽어줍니다.

마찬가지로 aria-describedby="dialog-desc"도 모달의 본문 설명을 제공하는 요소의 id를 참조하여, 스크린 리더가 다이얼로그의 내용을 더 자세히 설명할 수 있도록 합니다.

// 합성 컴포넌트로 구현된 다이얼로그 사용 모습, Header, Content에 이미 id 값이 있다.
<Dialog open={openDeleteAlert} onClose={deleteAlertClose}>
	<Dialog.Header> // id: "dialog-title" => 레이블 역할
  	  피드백을 삭제하시겠습니까?
    </Dialog.Header>
	<Dialog.Content> // id: "dialog-desc" => 본문 설명
      삭제한 피드백은 복구할 수 없습니다.
    </Dialog.Content>
    <Dialog.Bottom>
      <S.Button onClick={deleteAlertClose}>
        취소
	  </S.Button>
	  <S.Button onClick={handleFeedbackDelete}>
        확인
	  </S.Button>
	</Dialog.Bottom>
</Dialog>

3. 결론

Lighthouse의 점수가 절대적인 기준은 아니지만, 약간의 노력으로 Accessibility 점수를 크게 향상시킬 수 있습니다.

Lighthouse가

프론트엔드 개발자는 사용자와 가장 가까이 있는 개발자로서 사용자의 입장에서 생각하는 것이 중요하다고 생각합니다. 일반적인 사용자뿐이 아니라 특수한 상황에 놓인 사용자들의 경험도 함께 고려한다면 더 많은 사용자에게 웹 콘텐츠를 동등하게 제공할 수 있을 것입니다.

profile
Frontend Developer | 기록되지 않은 것은 기억되지 않는다

0개의 댓글

관련 채용 정보