Dialog 접근성 활용

Jung taeWoong·2021년 5월 30일
2

웹 접근성

목록 보기
1/2
post-thumbnail

모달(dialog) 접근성

React app 개발시 모달 component의 접근성을 높이는 방법을 알아보자

개발자도구에서 접근성 확인하는 방법
Elements Tab -> 우측 Accessibility Tab을 활용

모달 컴포넌트
모달 컴포넌트는 UI 디자인 개념에서 모달 다이얼로그(Dialog, 대화상자)로 불리며 사용자와 인터렉션한다.
모달 컴포넌트가 활성화되면 애플리케이션 위에 오버레이(overlay)되며, 다른 부분의 영역과 인터렉션 할 수 없도록 비활성화 처리된다.

모달 접근성 활용

  • role: 위젯, 구조 및 동작에 대한 의미 정보를 올바르게 전달하기 위해 사용
  • aria-labelledby: ARIA 속성을 사용해 레이블 역할의 요소와 연결하는 것
  • aria-haspopup: 스크린 리더 사용자에게 해당 요소가 하위메뉴를 포함하고 있다는(팝업될 수 있다는) 정보를 제공합니다.
  • aria-pressed: 스크린리더는 사용자에게 해당 요소를 버튼이 아닌 토글버튼이라고 읽어주며, 선택/미선택에 대한 상태 정보도 제공합니다.
return {
  <OpenButton 
  	aria-haspopup="true"
  	aria-pressed={this.state.isPressed}
    />
  <Modal
    role="dialog" // 모달의 역할을 명시
    /* 임의의 고유 ID 값을 부여해 제목역할을 수행하는 요소와 연결 */   
    aria-labelledby="modal-jtw" 
    > 
    /* 제목역할을 수행하는 컴포넌트에 동일한 ID값을 설정 */
    <Header id="modal-jtw" ... /> 
  </Modal>
}

ARIA 상태 업데이트
ARIA 상태 속성은 컴포넌트가 업데이트 될 때 현재 상태를 변경해야 한다.
aria-pressed 상태 속성 값이 눌러졌을때 true, 눌러지지 않았을 경우 false로 상황에 따라 업데이트 되어야 한다.

<a
  role="button"
  href="#toggleDialogButton"
  aria-haspopup="true"
  aria-pressed={this.state.isPressed}

  다이얼로그 보기
</a>

Focus 관리

기본으로 모달 컴포넌트가 보이는 상황에서 tab은 모달 바깥으로 활성화된다.

  • tabIndex: 요소의 tab순서를 지정하는 속성,
    • tabIndex = 0 지정시 focus을 받을 수 없는 h1, div 등과 같은 요소들도 tab으로 focus을 받을 수 있도록 처리됨
    • tabIndex = -1지정시 기본적으로 focus을 받는 태그가 tab을 못받도록 처리
return (
  <Modal
    role="dialog" // 모달의 역할을 명시
    /* 임의의 고유 ID 값을 부여해 제목역할을 수행하는 요소와 연결 */
    aria-labelledby="modal-jtw" 
    tabIndex="0" // 기본적으로 초점을 받을 수 없는 요소에 focus을 받을 수 있게 처리
    > 
    /* 제목역할을 수행하는 컴포넌트에 동일한 ID값을 설정 */
    <Header id="modal-jtw" ... /> 
  </Modal>
)

focus 이동이란?

  • 키보드 접근성이란 스크린 리더 사용자가 키보드를 통해 웹 페이지의 정보에 접근하는 것을 의미
  • 특히, focus이동이란 [tab]키를 누르면 좌측상단부터 focus받는 요소로 부터 이동되고, [shift] + [tab]키를 누르면 반대로 올라가는 것을 의미

기본적으로 focus을 받는 태그
HTML 태그 중 기본적으로 focus을 받는 것들이 있다.
a, button, input, select, textarea element

모달 open 시 focuse 설정

모달을 열었을 때 모달 컴포넌트로 focus 설정하면 모달 내부로 자연스럽게 탐색 가능

  • ref를 모달과 연결하여 focus 설정
const modalRef = useRef(null);

// 모달 열기 핸들러
const handleOpen = useCallback(() => {
  setIsOpen(true);
  // 시간차를 두고 포커스 설정
  window.setTimeout(() => modalRef.current.ref.current.Focus());
}, []);

return (
  <Modal
    ref={modalRef} // modalRef 연결
    role="dialog" // 모달의 역할을 명시
    /* 임의의 고유 ID 값을 부여해 제목역할을 수행하는 요소와 연결 */
    aria-labelledby="modal-jtw" 
    tabIndex="0" // 기본적으로 초점을 받을 수 없는 요소에 focus을 받을 수 있게 처리
    > 
    /* 제목역할을 수행하는 컴포넌트에 동일한 ID값을 설정 */
    <Header id="modal-jtw" ... /> 
  </Modal>
  )

setTimeout을 사용한 이유는 클래스 컴포넌트의 this.setState와 달리, useState 훅은 콜백 함수 처리 기능이 없기 때문입니다.

모달을 닫았을때 focus 지정

모달을 닫고나서 focus를 모달을 여는 트리거를 갖는 버튼 으로 지정

const openButtonRef = useRef(null);

const handleClose = useCallback(() => {
  setIsOpen(false);
  // 모달을 닫았을 때 시간차를 두고 openButtonRef의 포커스 이동 설정
  window.setTimeout(() => openButtonRef.current.ref.current.focus());
}, []);

return (
  <OpenButton 
    ref={openButtonRef}  //openButtonRef 설정
    onClick={handleOpen}
    >
    Open
  </OpenButton>
)

모달 내부 탐색 순환

모달 내부에 focus 이동 요소들을 순환처리(첫번째 focus 요소와 마지막 focus 요소에 이벤트 연결

키보드 트랩이란?
Modal 컴포넌트가 활성화 된 상태일 동이나, 키보드 포커스가 컴포넌트 외부로 빠져나가지 못하도록 가두는 것을 말합니다. 키보드 사용자가 Tab 키를 눌러 탐색 시, 의도치 않게 Modal 밖으로 벗어나지 못하게 함으로서 접근성을 높이는 것입니다.

const modalRef = useRef(null);
const closeButtonRef = useRef(null);

// 모달 포커스 이동 핸들러
const handleFocusModal = useCallback((e) => {
  /* [shift]키를 누르지 않고 [tab}키만 눌럿을 때 */
  if(!e.shiftKey && e.keyCode === 9) {
    e.preventDefault(); // 기본 이벤트 동작 막기
    modalRef.current.ref.current.focus(); // modalRef로 포커스 이동
  }
},[]);

const handleFocusCloseButton = useCallback((e) => {
  /* [shift]키와 [tab}키를 동시에 눌럿을 때 */
  if(e.shiftKey && e.keyCode === 9) {
    e.preventDefault(); // 기본 이벤트 동작 막기
    closeButtonRef.current.ref.current.focus(); // closeButtonRef로 포커스 이동
  }
},[]);

return (
  <ModalContent>
    <FirstButton 
      ref={modalRef}
      // 첫번째 focuse 이동 요소에 keydown 이벤트(handleFocusCloseButton) 연결
      onKeyDown={handleFocusCloseButton}
      />
    <FinalButton 
      ref={closeButtonRef}
      // 마지막 focus 이동 요소에 keydown 이벤트(handleFocusModal) 연결
      onKeyDown={handleFocusModal}
      />
  </ModalContent>
  
)

모달 닫기 ESC Key 연결

모달이 열린 상태일 때 esc를 누르면 모달이 닫혀야함

const handleClose = useCallback(() => {
  setIsOpen(false);
  // 모달을 닫았을 때 시간차를 두고 openButtonRef의 포커스 이동 설정
  window.setTimeout(() => openButtonRef.current.ref.current.focus());
}, []);

useEffect(
  () => {
    // Esc 키 메뉴 닫기 핸들러(포함)
    const handleEscCloseModal = (e) => {
      // esc를 누르면(true) handleClose 메소드 실행
      e.keyCode === 27 && handleClose();
    }
    
    const methodName = isOpen ? 'addEventListener' : 'removeElementListener';
    window[methodName]('keydown', handleEscCloseModal);
  },
  [ isOpen, handleClose ]
);
profile
Front-End 😲

0개의 댓글