본문 바로가기

카테고리 없음

웹 접근성을 고려하여 AI 채팅에 적용하기

웹 접근성❓

웹 접근성(Web Accessibility) 이란 모든 사용자가 모든 기기에서 웹 플랫폼 상의 정보에 쉽게 접근할 수 있도록 정의해 놓은 전문적인 규칙들이다.

 

비장애인은 웹사이트를 이용할 때 전혀 불편함 없이 이용하지만, 장애인은 그렇지 않다. 눈으로 볼 수 없는 경우도 있을 것이고, 마우스를 사용하지 못하는 경우도 있을 것이다.

 

최근에 개발 동료가 웹 접근성에 대해서 알려줬고 스크린리더에 대해서도 처음 알게 됐다. 그래서 현재 개발 중인 서비스에 적용해 보기로 했다.

 

우선 마우스를 사용하지 못하면 어떻게 웹 사이트를 사용해야 될까? 키보드 만으로 사용할 수 있도록 개발해야 된다. 

키보드로 웹사이트를 사용할때는 클릭 가능한 요소들을 Tab 버튼으로 탐색하게 된다. 

 

<a>, <button> 등의 HTML 태그나 Next에서 제공하는 <Link>와 같은 라우팅 전용 컴포넌트는 따로 설정을 하지 않아도 Tab을 누르면 탐색할 수 있도록 해당 요소에 포커스 된다.

 

하지만 불가피하게 <div>와 같은 요소를 Tab을 통해 탐색 가능하게 하고, 엔터를 눌렀을 때 마우스 클릭과 같이 입력되게 하려면 tabIndex 속성을 사용해야 된다.

<div tabIndex={tabIndex}>...</div>

하지만 웬만해서는 클릭 가능한 요소에는 div 같은 태그를 사용하는 것은 웹 접근성에 좋지 못한 방법이다.

 

 

맥북에는 VoiceOver라는 스크린 리더가 기본적으로 깔려있다. 직접 보이스오버를 실행해서 사용해 보면 굉장히 어색하고 불편하다. 우리가 이렇게 불편한 만큼 웹 접근성을 고려하여 개발을 한다면 스크린리더를 사용해야 하는 사용자는 더욱 편하게 사용할 수 있을 것이다.

 

개발 중인 서비스는 AI 채팅 서비스였기 때문에 채팅을 전송하고 답변이 오면 바로 답변 메시지를 읽어주는 것이 좋은 flow일 텐데 처음에는 사진과 같이 웹 콘텐츠에 있다는 메시지를 읽어주는 상황이었다.

 

채팅을 종료하고 대화 요약 화면으로 이동했을때도 요약 내용을 읽어주지 않고 아래와 같이 웹 콘텐츠라는 메시지만 읽어줬다. 이렇게 되면 화면을 볼 수 없는 사용자는 대화 내용 요약에 대해서 알 수 없게 된다.

 

해결하기 위해 2가지의 aria 속성을 사용했다.

 

aria-live

  • 동적으로 변경되는 콘텐츠를 스크린 리더가 즉시 읽어주도록 해주는 역할
  • "polite": 사용자가 현재 읽고 있는 내용을 방해하지 않고 자연스럽게 읽어줌
  • "assertive": 즉시 읽어줘야 하는 중요한 정보를 강제로 읽어줌(에러 알림, 긴급한 안내 사항 등에 사용)

aria-atomic

  • true: 변경된 요소 전체를 읽어줌
  • false: 변경된 부분만 읽어줌
// src/app/chat/MessageList.tsx
<div
  className="relative"
  style={{ height: `${rowVirtualizer.getTotalSize()}px` }}
  aria-live="polite"
  aria-atomic="false"
>
  {items.map((item) => {
    const message = messagesWithTrigger[item.index];
    return (
      <div
        key={item.key}
        data-index={item.index}
        ref={rowVirtualizer.measureElement}
        style={{
          position: "absolute",
          top: 0,
          left: 0,
          width: "100%",
          transform: `translateY(${item.start}px)`,
        }}
      >
        <MessageWrapper type={message.type} content={message.message}>
          {message.type === "USER" ? (
            <UserMessage content={message.message} />
          ) : (
            <AiMessage content={message.message} />
          )}
        </MessageWrapper>
      </div>
    );
  })}
</div>;

 

 

이 두 속성을 적용하고 나니까 채팅을 입력하고 답변이 왔을 때 바로 토리가 보내준 메시지를 스크린리더가 읽어주도록 더 자연스러운 흐름으로 바뀌었다.

 

 

채팅 종료 후 대화 요약 화면으로 이동했을때는 바로 요약 내용을 읽어주는 것이 자연스러울 거다.

aria-live='assertive'와 함께 조건부로 렌더링되는 대화 요약 내용 summary에 포커스를 주기 위해 tabIndex와 useRef, useEffect를 통해 포커스를 줘서 처리했다.

const ChatMain = ({
  chatMode,
  summary,
  messages,
  isLoading,
}: {
  chatMode: ChatMode
  messages: MessageType[]
  summary: string
  isLoading: boolean
}) => {
  const summaryRef = useRef<HTMLParagraphElement>(null)

  useEffect(() => {
    if (chatMode === 'end' && summaryRef.current) {
      summaryRef.current.focus()
    }
  }, [chatMode])

  return chatMode === 'end' ? (
    <main className='flex flex-1 flex-col items-center justify-between overflow-y-hidden px-24 pb-0 pt-40'>
      <p
        ref={summaryRef}
        tabIndex={-1}
        aria-live='assertive'
        className='scrollbar-bar-hidden overflow-y-auto text-16-600 [&::-webkit-scrollbar]:hidden'
      >
        {summary}
      </p>
    </main>
  ) : (
    <MessageList
      messages={messages}
      isLoading={isLoading}
      chatMode={chatMode}
    />
  )
}

 

적용하고 나니까 “웹 콘텐츠”라고 읽어주던 스크린 리더가 제대로 대화 요약 내용을 읽어준다.

 


 

이렇게 직접 스크린 리더기도 직접 사용하면서 웹 접근성을 고려해서 개발을 해봤다.

 

대기업에서는 이런 웹 접근성에 맞게 개발을 하는 인원을 따로 둘 정도로 이런 부분까지 모두 고려해서 웹 사이트 개발을 한다고 들었다.

웹 개발자라면 UX를 신경써야 하고, UX는 특정 대상에만 치중되면 안 되기 때문에 모두가 편리한 사용자 경험을 할 수 있도록 웹 접근성에 따른 개발 방법을 점진적으로 늘려가며 서비스에 적용해 봐야겠다.