"일꾼이 일을 잘하려면 먼저 도구를 갈고 닦아야 한다." - 공자, 『논어』.
첫 장 > 프로그램 작성 > React Hooks를 사용하여 접근 가능한 탐색 메뉴바 만들기

React Hooks를 사용하여 접근 가능한 탐색 메뉴바 만들기

2024-08-25에 게시됨
검색:860

Building an Accessible Navigation Menubar with React Hooks

실수로 게시되었습니다! 나중에 다시 방문해 주세요!

소개

접근 가능한 웹 애플리케이션을 만드는 것은 단지 좋은 습관이 아니라 이제는 필수입니다. 최근에 a11y를 중심으로 네비게이션 메뉴바를 만들 기회가 있었습니다. 조사하면서 나는 대부분의 메뉴바가 ARIA 패턴을 따르지 않는다는 사실을 깨달았습니다. 예를 들어 메뉴 항목을 탭으로 이동하는 대신 메뉴 모음을 화살표 키로 탐색하고 자체 포커스를 관리해야 한다는 것을 알고 계셨나요?

일부 튜토리얼을 찾았지만 결국 완전히 따르지는 못했습니다. 제가 이 글을 쓰는 이유는 제가 만든 것이 공유할 가치가 있다고 생각하기 때문입니다. 여러분도 작은 구성요소와 사용자 정의 후크에 관심이 있다면 말이죠.

이 블로그를 몇 가지 개발 단계로 구성하겠지만, 단계별 가이드를 작성하는 것이 목표는 아닙니다. 나는 당신이 React의 기본과 사용자 정의 후크의 작동 방식을 알고 있다고 믿습니다.

지금은 주요 구현 세부정보만 공유하고 있지만 나중에 시간이 더 있으면 코드 샌드박스 예제로 이 문서를 업데이트할 계획입니다.

우리는 무엇을 만들고 있나요?

이 블로그에서는 많은 웹 애플리케이션의 상단이나 측면에 표시되는 것과 같은 탐색 메뉴 표시줄을 구축하고 있습니다. 이 메뉴 표시줄에서 일부 메뉴 항목에는 마우스 입력/나가기 시 열리거나 닫히는 하위 메뉴가 있을 수 있습니다.

HTML 마크업

무엇보다도 의미론적 HTML과 적절한 역할 및 ARIA 속성이 접근성에 필수적입니다. 메뉴바 패턴에 대한 자세한 내용은 여기 공식 문서에서 확인할 수 있습니다.

다음은 적절한 HTML 마크업의 예입니다.

의미론적 HTML을 위해 버튼 태그를 사용하고 있다는 점에 주목하세요. 버튼에는 스크린 리더에게 경고를 보내는 aria-haspopup도 있어야 합니다. 마지막으로 메뉴 상태에 따라 적절한 aria-expanded 속성을 지정해야 합니다.

구성요소

필요한 구성요소를 살펴보겠습니다. 당연히 메뉴 항목 구성요소뿐 아니라 전체 메뉴 구성요소도 필요합니다.

일부 메뉴 항목에는 하위 메뉴가 있고 일부에는 하위 메뉴가 없습니다. 하위 메뉴가 있는 메뉴 항목은 마우스 오버 및 키보드 이벤트 시 하위 메뉴 열기/닫기 상태를 관리해야 합니다. 따라서 자체 구성요소여야 합니다.

하위 메뉴도 자체 구성요소여야 합니다. 하위 메뉴도 메뉴 항목의 컨테이너일 뿐이지만 상태를 관리하거나 키보드 이벤트를 처리하지는 않습니다. 이는 최상위 탐색 메뉴와 구별됩니다.

결국 다음 구성요소를 작성하게 되었습니다.

  • 메뉴 모음의 가장 바깥쪽 레이어에 대한 NavMenu입니다.
  • 개별 메뉴 항목에 대한 MenuItem입니다.
    • 메뉴항목링크
    • 하위 메뉴가 있는 메뉴 항목
  • 확장된 하위 메뉴에 대한 하위 메뉴입니다. MenuItem은 하위 메뉴 내에 재귀적으로 중첩될 수 있습니다.

집중관리

매우 쉽게 말하면 "포커스 관리"는 구성 요소가 어떤 자식에 포커스가 있는지 알아야 함을 의미합니다. 따라서 사용자의 포커스가 떠났다가 다시 돌아오면 이전에 포커스가 맞춰진 하위 항목에 다시 포커스가 맞춰집니다.

포커스 관리를 위한 일반적인 기술은 "로빙 탭 인덱스"입니다. 여기서 그룹의 포커스된 요소는 탭 인덱스가 0이고 다른 요소의 탭 인덱스는 -1입니다. 이렇게 하면 사용자가 포커스 그룹으로 돌아갈 때 탭 인덱스가 0인 요소에 자동으로 포커스가 부여됩니다.

NavMenu의 첫 번째 구현은 다음과 같습니다.

export function NavMenu ({ menuItems }) {
  // state for the currently focused index
  const [focusedIndex, setFocusedIndex] = useState(0);

  // functions to update focused index
  const goToStart = () => setCurrentIndex(0);
  const goToEnd = () => setCurrentIndex(menuItems.length - 1);
  const goToPrev = () => {
    const index = currentIndex === 0 ? menuItems.length - 1 : currentIndex - 1;
    setCurrentIndex(index);
  };
  const goToNext = () => {
    const index = currentIndex === menuItems.length - 1 ? 0 : currentIndex   1;
    setCurrentIndex(index);
  };

  // key down handler according to aria specification
  const handleKeyDown = (e) => {
    e.stopPropagation();
    switch (e.code) {
      case "ArrowLeft":
      case "ArrowUp":
        e.preventDefault();
        goToPrev();
        break;
      case "ArrowRight":
      case "ArrowDown": 
        e.preventDefault();
        goToNext();
        break;
      case "End":
        e.preventDefault();
        goToEnd();
        break;
      case "Home":
        e.preventDefault();
        goToStart();
        break;
      default:
        break;
    }
  }

  return (
    
  );
}

e.preventDefault()는 ArrowDown이 페이지를 스크롤하는 것과 같은 일을 방지하기 위해 존재합니다.

여기 MenuItem 구성 요소가 있습니다. 하위 메뉴가 있는 항목은 잠시 무시해 보겠습니다. focusIndex가 변경될 때마다 useEffect, usePrevious 및 element.focus()를 사용하여 요소에 초점을 맞추고 있습니다.

export function MenuItem ({ item, index, focusedIndex, setFocusedIndex }) {
  const linkRef = useRef(null);
  const prevFocusedIndex = usePrevious(focusedIndex);
  const isFocused = index === focusedIndex;

  useEffect(() => {
    if (linkRef.current 
      && prevFocusedIndex !== currentIndex 
      && isFocused) {
      linkRef.current.focus()
    }
  }, [isFocused, prevFocusedIndex, focusedIndex]);

  const handleFocus = () => {
    if (focusedIndex !== index) {
      setFocusedIndex(index);
    }
  };

  return (
    
  • {item.label}
  • ); }

    ref(하위 메뉴가 있는 메뉴 항목에 대한 버튼)가 있어야 하는 태그이므로 해당 태그에 초점을 맞추면 Enter 키 탐색과 같이 기본 키보드 동작이 예상대로 작동합니다. 게다가 포커스가 있는 요소에 따라 탭 인덱스가 제대로 할당되어 있습니다.

    포커스 이벤트가 키/마우스 이벤트에서 발생하지 않는 경우를 대비해 포커스 이벤트에 대한 이벤트 핸들러를 추가하고 있습니다. 다음은 웹 문서의 인용문입니다:

    모든 포커스 변경이 키 및 마우스 이벤트를 통해 이루어질 것이라고 가정하지 마십시오. 스크린 리더와 같은 보조 기술은 포커스 가능한 요소에 포커스를 설정할 수 있습니다.

    조정 #1

    위에 설명된 useEffect를 따르면 사용자가 키보드를 사용하여 탐색하지 않은 경우에도 첫 번째 요소에 포커스가 있음을 알 수 있습니다. 이 문제를 해결하려면 활성 요소를 확인하고 사용자가 키보드 이벤트를 시작한 경우에만 focus()를 호출하여 초점을 본문에서 다른 곳으로 이동할 수 있습니다.

      useEffect(() => {
        if (linkRef.current 
          && document.activeElement !== document.body // only call focus when user uses keyboard navigation
          && prevFocusedIndex !== focusedIndex
          && isCurrent) {
          linkRef.current.focus();
        }
      }, [isCurrent, focusedIndex, prevFocusedIndex]);
    

    로직 재사용 및 사용자 정의 후크

    지금까지 기능적인 NavMenu 및 MenuItemLink 구성 요소가 있었습니다. 하위 메뉴가 있는 메뉴 항목으로 넘어가겠습니다.

    빠르게 구축하면서 이 메뉴 항목이 대부분의 로직을 공유한다는 것을 깨달았습니다.

    릴리스 선언문 이 기사는 https://dev.to/godsamit/building-an-accessible-navigation-menubar-with-react-hooks-blh?1 에서 복제됩니다. 침해가 있는 경우, [email protected]에 연락하여 삭제하시기 바랍니다. 그것
    최신 튜토리얼 더>

    부인 성명: 제공된 모든 리소스는 부분적으로 인터넷에서 가져온 것입니다. 귀하의 저작권이나 기타 권리 및 이익이 침해된 경우 자세한 이유를 설명하고 저작권 또는 권리 및 이익에 대한 증거를 제공한 후 이메일([email protected])로 보내주십시오. 최대한 빨리 처리해 드리겠습니다.

    Copyright© 2022 湘ICP备2022001581号-3