아티클

next js 컴포넌트에서 사용 가능한 접근성을 고려한 BottomSheetAccessibilityManager 컴포넌트 구현 가이드

엔비전스 접근성 2025-01-13 10:56:48

next js 컴포넌트에서 사용 가능한 접근성을 고려한 BottomSheetAccessibilityManager 컴포넌트 구현 가이드

안녕하세요, 엔비전스 입니다.

바텀시트는 화면의 하단에서 나타나는 UI 요소로, 사용자와의 상호작용을 위해 정보를 표시하거나 작업을 제공하는 데 사용됩니다. 바텀시트가 나타나면 기존 콘텐츠가 완전히 숨겨지지 않고 흐릿하게 보이게 되는데, 이는 바텀시트와의 상호작용이 끝난 후 사용자가 즉��� 원래 콘텐츠로 복귀할 수 있도록 자연스러운 전환을 제공하기 위함입니다. 이 접근 방식은 화면의 맥락을 유지하면서도 사용자가 현재 작업에 집중하도록 돕는 데 효과적입니다.

그러나 이와 같은 bottom sheet 구현 방법은 접근성 측면에서 몇 가지 문제를 일으킵니다. 첫째, 키보드 및 스크린 리더에서는 바텀시트를 포함한 모든 영역에 다 동일하게 접근되기 때문에 흐릿하게 비활성화 되어 보여지는 배경 요소들과 바텀시트를 명확히 구분하기 어려워 문제가 있습니다. 둘째, 바텀시트가 화면에 나타나더라도 바텀시트 영역으로 키보드 초점이 이동하지 않아 콘텐츠가 변경되었는지조차 알 수 없는 문제가 있습니다. 마지막으로, 바텀시트를 닫았을 때 포커스가 기존 요소로 돌아오지 않아 연속적인 탐색이 불가능해지는 문제가 발생합니다. 이러한 문제들은 접근성을 개선하기 위해 반드시 해결해야 합니다.

따라서 요즘 next JS가 웹 개발에서 많이 사용되고 있는 점을 감안하여 bottomSheet 구현 시 나타나는 접근성 문제를 해결할 수 있는 컴포넌트를 개발하여 소개하려 합니다.

BottomSheetAccessibilityManager의 특징

1. 외부 요소 접근 제어

  • inert 속성을 사용하여 바텀시트 외부의 모든 요소들에 대한 접근을 차단합니다.
  • 스크린 리더 사용자가 바텀시트 내부 콘텐츠에만 집중할 수 있도록 돕습니다.

2. 포커스 관리

  • 바텀시트가 열릴 때 지정된 요소(initialFocusElementId)로 포커스가 자동 이동합니다.
  • 지정된 요소가 없다면 첫 번째 포커스 가능한 요소로 이동합니다.
  • 바텀시트가 닫힐 때 이전 포커스 위치로 자연스럽게 복귀합니다.

3. 쉬운 적용

  • 기존 바텀시트 컴포넌트를 감싸는 것만으로도 접근성 개선이 가능합니다.
  • Next.js 환경에서 SSR을 고려한 안전한 구현을 제공합니다.

BottomSheetAccessibilityManager 적용하기

1. 컴포넌트 추가 및 import

BottomSheetAccessibilityManager.tsx 파일을 다운로드하여 프로젝트의 components 폴더에 추가하고 import 합니다.

import { BottomSheetAccessibilityManager } from '@/components/BottomSheetAccessibilityManager';

2. 상태 분리하기

바텀시트가 닫힐 때 inert 속성을 안전하게 제거하기 위해서는 상태를 분리해야 합니다. 컴포넌트가 즉시 언마운트되면 cleanup 함수가 제대로 실행되지 않아 inert 속성이 남게 되므로, 정리 작업을 위한 시간이 필요합니다.

// 기존 코드
const [isOpen, setIsOpen] = useState(false);

// 수정된 코드
const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);    // 마운트 상태
const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(false);  // 시각적 표시 상태

3. 열기/닫기 핸들러 수정

const handleOpen = () => {
  setIsBottomSheetOpen(true);      // 먼저 마운트
  setIsBottomSheetVisible(true);   // 그 다음 표시
};

const handleClose = useCallback(() => {
  setIsBottomSheetVisible(false);  // 먼저 시각적으로 숨김
  setTimeout(() => {
    setIsBottomSheetOpen(false);   // 300ms 후 언마운트
  }, 300);  // inert 속성 제거를 위한 시간
}, []);

4. BottomSheetAccessibilityManager 적용

{isBottomSheetOpen && (
  <BottomSheetAccessibilityManager
    isOpen={isBottomSheetOpen}
    bottomSheetId="my-bottom-sheet"
  >
    <div
      id="my-bottom-sheet"
      role="dialog"
      aria-modal="true"
      aria-labelledby="bottom-sheet-title"
      className={`bottom-sheet ${isBottomSheetVisible ? 'visible' : 'hidden'}`}
    >
      <h2 id="bottom-sheet-title">바텀시트 제목</h2>
      {/* 바텀시트 내용 */}
    </div>
  </BottomSheetAccessibilityManager>
)}

5. 초기 포커스 설정 (선택사항)

특정 요소에 초기 포커스를 주고 싶은 경우 initialFocusElementId를 설정할 수 있습니다.

<BottomSheetAccessibilityManager
  isOpen={isBottomSheetOpen}
  bottomSheetId="my-bottom-sheet"
  initialFocusElementId="close-button"
>
  <div id="my-bottom-sheet" role="dialog" aria-modal="true">
    <button id="close-button" onClick={handleClose}>
      닫기
    </button>
    {/* 바텀시트 내용 */}
  </div>
</BottomSheetAccessibilityManager>

6. 전체 적용 예시

const BottomSheet = () => {
  const [isBottomSheetOpen, setIsBottomSheetOpen] = useState(false);
  const [isBottomSheetVisible, setIsBottomSheetVisible] = useState(false);

  const handleClose = useCallback(() => {
    setIsBottomSheetVisible(false);
    setTimeout(() => {
      setIsBottomSheetOpen(false);
    }, 300);
  }, []);

  return (
    <>
      {isBottomSheetOpen && (
        <BottomSheetAccessibilityManager
          isOpen={isBottomSheetOpen}
          bottomSheetId="my-bottom-sheet"
          initialFocusElementId="close-button"
        >
          <div
            id="my-bottom-sheet"
            role="dialog"
            aria-modal="true"
            aria-labelledby="bottom-sheet-title"
            className={`bottom-sheet ${isBottomSheetVisible ? 'visible' : 'hidden'}`}
          >
            <h2 id="bottom-sheet-title">바텀시트 제목</h2>
            <button id="close-button" onClick={handleClose}>
              닫기
            </button>
            {/* 바텀시트 내용 */}
          </div>
        </BottomSheetAccessibilityManager>
      )}
    </>
  );
};

이렇게 적용하면 바텀시트의 접근성이 자동으로 관리되며, 스크린리더 사용자들도 원활하게 바텀시트를 이용할 수 있습니다.

댓글 0
댓글을 작성하려면 해주세요.