웹 접근성❓
웹 접근성(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는 특정 대상에만 치중되면 안 되기 때문에 모두가 편리한 사용자 경험을 할 수 있도록 웹 접근성에 따른 개발 방법을 점진적으로 늘려가며 서비스에 적용해 봐야겠다.