GitHub
보이지 않는 경험에 대하여

보이지 않는 경험에 대하여

요즘엔 ChatGPT와 같은 대화형 인공지능과 Cursor와 같은 AI 기반 IDE 등 다양한 서비스가 등장하고 있습니다. 그중에서도 특히 흥미로웠던 경험은 ChatGPT를 음성으로 사용하는 순간이었습니다. 마치 사람이 직접 대화하는 것처럼 자연스러운 응답을 들으며, 문득 궁금해졌습니다. 시각장애인 사용자들은 웹을 어떻게 경험할까?

접근성(a11y)

웹 개발에서의 접근성이란 사람들의 능력이나 장애에 상관없이 모든 사용자가 웹을 이해하고 사용할 수 있도록 보장하는 것입니다. 현대 사회에서 웹은 정보 탐색부터 예약, 쇼핑, 배달까지 다양한 활동의 중심이 되었습니다. 특히 시각장애인 사용자들에게 웹은 어떤 경험으로 다가올까?

웹 접근성에서 가장 중요한 두 가지 요소는 **Semantic (의미론적 HTML)**과 **ARIA (Accessible Rich Internet Applications)**입니다.

스크린 리더는 시각 정보를 음성으로 읽어주는 보조 기술입니다. 하지만 웹 페이지가 단순히 시각적 요소로만 구성되어 있다면, 스크린 리더는 이를 제대로 이해하지 못합니다. 예를 들어, 시각적으로는 “비밀번호는 특수문자를 포함한 11자 이상”이라는 안내가 플레이스홀더로 제공되어도 스크린 리더는 이를 읽어주지 않습니다. 이 때 ARIA 속성을 이용하여 내용을 설명하거나 동적인 메세지를 즉시 읽어줄 수 있도록 하여 정보를 제공하죠.

그러나 Placeholder는 읽히지 않는다

웹 사이트에서는 입력란에 해당 입력란이 정상적으로 처리될 수 있는 조건(e.g. 비밀번호 입력란이라면 ‘특수문자를 포함한 11자 이상 입력해주세요’와 같은) 안내가 placeholder로 제공됩니다. 이는 시각적으로는 훌륭한 UX를 제공하지만, 시각장애인 사용자에게는 아무런 의미가 없습니다. 스크린 리더는 기본적으로 placeholder의 텍스트를 읽어주지 않기 때문입니다. 이는 웹 접근성 표준에서 placeholder가 보조 안내일 뿐, 사용자에게 중요한 정보로 간주하지 않기 때문입니다. 최근 대부분의 웹 사이트는 소셜 로그인 기능을 기본적으로 지원하고 있기 때문에 상관없는거 아닌가? 라고 생각할 수 있겠지만 이는 보편적 디자인(Universal Design)의 철학을 무시하는 이기적인 접근일 수 있습니다.

화면을 눈으로 볼 수 있는 사용자가 입력 조건을 확인하고 한 번에 이용할 수 있다면, 시각장애인 사용자 또한 이러한 입력 조건을 인지하고 한 번에 이용할 수 있어야 합니다. 사용자의 능력과 장애에 상관없이 일관적인 경험을 제공해야 한다고 생각하기 때문입니다.

ARIA로 읽어주자

사용자에게 중요한 안내는 placeholder 대신 aria-label 또는 aria-describedby로 제공해야 합니다. 하지만 매번 각 <input>에 ARIA 속성을 직접 추가하는 것은 번거롭고 실수할 수 있습니다. 이를 해결하기 위해 React에서 자동으로 ARIA를 적용할 수 있도록 컴포넌트를 만들어 보는 건 어떨까요?

import React, { ReactNode, cloneElement, ReactElement, useId, Children } from "react";

interface ARIAInputProps {
  children: ReactNode;
  descriptionId?: string;
}

const ARIAInput: React.FC<ARIAInputProps> = ({ children, descriptionId }) => {
  const generatedId = useId();

  const enhancedChildren = Children.map(children, (child) => {
    if (React.isValidElement(child) && child.type === "input") {
      const placeholder = child.props.placeholder;
      const inputId = child.props.id;
      const labelElement = document.querySelector(`label[for='${inputId}']`);
      const labelId = labelElement ? labelElement.getAttribute("id") : null;
      const descriptionIdFinal = descriptionId || `${generatedId}-desc`;

      return (
        <>
          {cloneElement(child as ReactElement, {
            ...child.props,
            "aria-labelledby": labelId || undefined,
            "aria-describedby": placeholder ? descriptionIdFinal : undefined,
          })}
          {placeholder && (
            <span id={descriptionIdFinal} style={{ display: "none" }}>
              {placeholder}
            </span>
          )}
        </>
      );
    }
    return child;
  });

  return <div>{enhancedChildren}</div>;
};

export default ARIAInput;

사용자에게 중요한 안내는 <label>은 제목으로, placeholder는 추가 설명으로 읽어줘야 합니다. 하지만 매번 각 <input>에 접근성 속성을 직접 추가하는 것은 번거롭고 실수할 수 있습니다. 이를 해결하기 위해 React에서 자동으로 ARIA를 적용하는 ARIAInput 레이아웃 컴포넌트로 만들어 보았습니다.

import React from "react";
import ARIAInput from "./components/ARIAInput";

function App() {
  return (
    <div>
      <label htmlFor="email" id="email-label">이메일</label>
      <ARIAInput>
        <input type="email" id="email" placeholder="example@example.com" />
      </ARIAInput>
      
      <label htmlFor="password" id="password-label">비밀번호</label>
      <ARIAInput>
        <input type="password" id="password" placeholder="특수문자를 포함한 11자 이상" />
      </ARIAInput>
    </div>
  );
}

이처럼 ARIAInput을 사용하면 스크린 리더는 label과 placeholder를 순서대로 읽어주어 시각장애인 사용자에게도 동일한 경험을 제공할 수 있지 않을까요? 여기서 더 나아가 textarea, select 등 다른 입력 요소에도 확장하고 aria-live까지 고려한다면 더 나은 UX를 제공할 수 있을 것이라 생각합니다.

마무리하며

시각장애인 사용자에 한정지어 설명했지만 접근성은 모든 사용자가 웹을 평등하게 경험할 수 있도록 보장하는 것입니다. 웹 접근성은 사용자에게 보이는 디자인뿐만 아니라 보이지 않는 사용자 경험을 설계하는 것이라고 생각합니다. 이렇게 글을 쓰다보니 여태 제가 디자인하고 개발했던 것들이 누군가를 배제하지 않았나라는 생각이 들면서 되돌아보는 시간을 가지기도 했습니다. 디자이너와 개발자 사이의 가교 역할을 할 수 있는 누군가가 되고 싶은 것이 제가 쌓고 싶은 커리어의 마지막 한 줄인만큼 앞으로도 더 나은 웹 환경을 만드는데 세심하게 고려할 수 있는 습관을 가져야 겠다고 생각하게 되는 하루였습니다.