Radix 라이브러리에서의 모달 대화상자 접근성 지원
안녕하세요, 엔비전스입니다. 현대 웹 개발에서는 Next.js와 같은 프레임워크를 통해 HTML, CSS, JavaScript를 보다 효율적이고 강력하게 활용할 수 있습니다. 이러한 프레임워크를 사용할 때 필요한 다양한 UI 컴포넌트를 제공하는 라이브러리 중 하나가 Radix 라이브러리입니다.
Radix 라이브러리란?
Radix 라이브러리는 Modulz 팀이 만든 웹 디자인 시스템 컴포넌트 라이브러리로, React 기반의 프레임워크와 잘 어우러집니다. Radix 라이브러리는 접근성, 성능, 스타일링을 모두 고려하여 다양한 UI 컴포넌트를 구현하였습니다.
모달 대화상자 컴포넌트의 접근성 지원
이번에는 Radix 라이브러리를 활용하여 모달 대화상자를 구현할 때 자체적으로 지원해주는 접근성 기능과 이를 커스터마이징할 때의 주의사항에 대해 살펴 보려고 합니다. 모달 대화상자는 사용자에게 중요한 정보나 작업을 전달하기 위해 화면 위에 나타나는 팝업 창입니다. 하지만 모달 대화상자는 스크린 리더나 키보드와 같은 보조 도구를 사용하는 사용자들에게 적절한 접근성 지원이 제공되지 않으면 큰 문제가 발생할 수 있습니다. 예를 들어, 모달 대화상자가 열렸을 때 대화상자 내부로 초점이 이동되지 않아 스크린 리더가 아무런 내용도 읽어주지 않거나, 키보드로 모달 대화상자 안과 가려진 바깥 영역을 자유롭게 이동할 수 있거나, 모달 대화상자가 닫힌 후에도 포커스가 기존 대화상자를 트리거하는 버튼으로 돌아오지 않는 것 등이 있습니다. Radix 라이브러리는 이런 접근성 문제를 예방하기 위해 모달 대화상자 컴포넌트에 필요한 ARIA 속성과 이벤트 핸들러를 자동으로 적용해줍니다.
<Dialog.Trigger>로 대화상자를 열 수 있는 버튼 구현하기
Radix 라이브러리에서는 <Dialog.Trigger> 컴포넌트를 사용하여 대화상자를 열 수 있는 버튼을 구현할 수 있습니다. 이때, Radix 라이브러리는 자동으로 aria-haspopup 속성을 버튼에 추가하여, 스크린 리더에게 이 버튼이 대화상자 팝업을 트리거한다는 것을 알려줍니다. 대화상자가 닫히면, 포커스는 원래의 버튼으로 자동으로 돌아갑니다.
대화상자를 여는 버튼은 버튼 태그이거나 role="button", tabindex="0" 속성이 있어야 합니다. <Dialog.Trigger> 요소가 아닌 별도 영역에 대화상자를 호출하는 버튼을 구현하는 경우에는 당연하게도 aria-haspopup="dialog" 속성 및 대화상자가 닫혔을 때 기존 요소로 초점을 되돌리는 부분을 직접 구현해 주어야 합니다.
<Dialog.Title>로 대화상자의 제목 제공하기
대화상자의 제목을 제공하기 위해서는 <Dialog.Title> 컴포넌트를 사용할 수 있습니다. 이 요소를 사용할 경우 role="dialog" 속성에 접근성 대화상자 제목이 aria-labelledby 속성을 통해 자동으로 매핑됩니다. 이렇게 하면, 스크린 리더는 어떤 대화상자가 열렸는지 대화상자 제목을 함께 읽어줍니다. 그러나 대화 상자 내부에 <Dialog.Title> 컴포넌트를 사용하지 않으면 당연히 role dialog 속성에 어떤 제목이 들어가야 할지 알 수 없으므로 이 역시 수동 구현이 필요합니다.
<Dialog.Description>으로 대화상자의 설명 제공하기
대화상자의 설명을 제공하기 위해서는 <Dialog.Description> 컴포넌트를 사용할 수 있습니다. 이 요소를 사용하면 role="dialog" 속성 내에서 aria-describedby 속성으로 자동으로 연결되어, 스크린 리더 사용자가 대화상자에 초점을 맞추는 경우 설명 텍스트 내용을 확인할 수 있습니다. 이는 사용자에게 대화상자의 추가적인 설명을 제공하여 대화상자를 보다 쉽게 이해할 수 있도록 돕습니다.
대화상자가 열릴 때 포커스 이동하기
대화상자가 열리면, Radix 라이브러리는 tabindex="0"이 삽입된 요소를 포함하여 포커스 가능한 요소를 찾아서 포커스를 보냅니다. 즉 포커스 가능한 요소가 존재한다면, 그 요소에 포커스가 자동으로 이동합니다. 포커스 가능한 요소가 없다면, 대화상자 컨테이너에 포커스가 이동합니다. 이렇게 하면, 대화 상자 내부로 초점이 이동하므로 모달 대화상자가 열렸음을 바로 인지할 수 있게 됩니다.
이런 방식으로 onOpenAutoFocus prop을 사용하고 기본 동작을 막으면 대화상자가 열릴 때의 포커스 관리를 완전히 제어할 수 있습니다. 어떤 요소에 포커스를 보낼지 선택하거나 아예 포커스를 보내지 않을 수도 있습니다.
하지만 기본 포커스 동작을 수정할 때는 접근성 문제를 고려해야 합니다. 키보드 조작에 의존하는 사용자를 포함하여 모든 사용자가 대화상자를 접근하고 사용할 수 있도록 해야 합니다. 따라서 기본 동작을 막는 경우에는 수동으로 반드시 대화상자 내부로 포커스를 보내주어야 합니다. 그렇지 않으면 대화상자가 열렸다는 것을 인식하기 어렵거나 아예 인식할 수 없게 될 수 있습니다.
대화 상자 바깥 탐색되지 않게 해 주기
화면에서 가려진 영역에 키보드 및 스크린 리더가 접근하지 못하게 하기 위해 Radix에서는 두 가지가 자동 적용됩니다.
- 대화상자 이 외의 영역에 aria-hidden="true"를 적용하여 스크린 리더에서 바깥 영역이 탐색되지 못하게 합니다.
- 탭, 쉬프트 탭을 눌렀을 때 대화상자 외부로 빠져나가는 순간 다시 대화상자 내부로 초점을 되돌립니다.
<Dialog.Root> 사용하기
모달 대화상자를 구현할 때 기본적으로 <Dialog.Root> 컴포넌트를 사용합니다. <Dialog.Root>는 모달 대화상자의 루트 요소로, 모달의 열림/닫힘 상태를 관리하고, 대화상자 관련 컴포넌트를 구조적으로 연결합니다. 이 컴포넌트는 모달의 상태와 접근성을 관리하며, 내부적으로 필요한 ARIA 속성들을 자동으로 설정합니다.
예를 들어, <Dialog.Root>는 대화상자가 열렸을 때 aria-hidden 속성을 조정하여 스크린 리더가 대화상자 외부의 콘텐츠를 읽지 않도록 합니다. 또한, 대화상자가 열리고 닫히는 시점에 이벤트 핸들러를 통해 포커스 관리를 수행하여 접근성을 보장합니다.
Radix 라이브러리를 사용한 대화상자 구현 샘플 프로젝트
Radix 라이브러리를 사용한 대화상자 구현 샘플 프로젝트를 공유합니다. 프로젝트 폴더에 압축을 풀고 터미널에서 npm install
명령어를 통해 관련 패키지를 설치할 수 있고 npm run dev
를 통해 실제 로컬 서버에서 테스트해볼 수 있습니다. 프로젝트 URL은 a11y-nvisions.github.io/next/dialog.zip입니다.
이렇게 Radix 라이브러리에서는 모달 대화상자를 구현하면서 접근성을 고려한 다양한 방법들을 적용하고 있습니다. 이러한 기법들은 보조 기기 사용자뿐만 아니라 모든 사용자에게 좀 더 친화적이고 일관된 사용자 경험을 제공할 수 있습니다.