"Se um trabalhador quiser fazer bem o seu trabalho, ele deve primeiro afiar suas ferramentas." - Confúcio, "Os Analectos de Confúcio. Lu Linggong"
Primeira página > Programação > Construindo uma barra de menu de navegação acessível com React Hooks

Construindo uma barra de menu de navegação acessível com React Hooks

Publicado em 2024-08-25
Navegar:829

Building an Accessible Navigation Menubar with React Hooks

Publicado acidentalmente! Volte mais tarde para saber mais!

Introdução

Criar aplicativos web acessíveis não é apenas uma boa prática — é uma necessidade agora. Recentemente, tive a oportunidade de construir uma barra de menu de navegação com foco no a11y. Enquanto pesquisava, percebi como a maioria das barras de menu não obedece ao padrão ARIA. Por exemplo, em vez de navegar pelos itens do menu, você sabia que uma barra de menu deve ser navegada com teclas de seta e gerenciar seu próprio foco?

Embora tenha encontrado alguns tutoriais, acabei não os seguindo completamente. Estou escrevendo isso porque acho que vale a pena compartilhar o que acabei construindo - se você também tiver afinidade com pequenos componentes e ganchos personalizados.

Embora eu vá estruturar este blog com algumas etapas de desenvolvimento, meu objetivo não é escrever um guia passo a passo. Confio que você conheça os fundamentos do React e como funcionam os ganchos personalizados.

Estou apenas compartilhando os principais detalhes de implementação agora, mas pretendo atualizar este artigo com um exemplo de sandbox de código no futuro, quando tiver mais tempo.

O que estamos construindo?

Para este blog, estamos construindo uma barra de menu de navegação, como as que você vê na parte superior ou lateral de muitos aplicativos da web. Nesta barra de menu, alguns itens de menu podem ter submenus, que abrirão/fecharão ao entrar/sair do mouse.

Marcação HTML

Em primeiro lugar, HTML semântico e funções apropriadas e atributos ARIA são essenciais para a acessibilidade. Para o padrão da barra de menu, você pode ler mais no documento oficial aqui.

Aqui está um exemplo de marcação HTML apropriada:

Observe que estamos usando a tag button para HTML semântico. O botão também deve ter aria-haspopup para alertar os leitores de tela. Por último, o atributo aria-expanded apropriado deve ser atribuído dependendo do estado do menu.

Componentes

Vamos examinar os componentes que precisamos. Obviamente, precisamos de um componente de menu geral, bem como de um componente de item de menu.

Alguns itens de menu possuem um submenu, enquanto outros não. Os itens de menu com submenus precisarão gerenciar seus estados para abrir/fechar submenus ao passar o mouse e em eventos de teclado. Portanto, precisa ser seu próprio componente.

Os submenus também precisam ser seus próprios componentes. Embora os submenus também sejam apenas contêineres para itens de menu, eles não gerenciam seus estados nem manipulam eventos de teclado. Isso os diferencia do menu de navegação de nível superior.

Acabei escrevendo estes componentes:

  • NavMenu para a camada mais externa das barras de menu.
  • MenuItem para itens de menu individuais.
    • MenuItemLink
    • MenuItemComSubMenu
  • SubMenu para o submenu expandido. MenuItem pode ser aninhado recursivamente no submenu.

Gerenciamento de foco

Em palavras muito simples, "gerenciamento de foco" significa apenas que o componente precisa saber qual criança tem foco. Assim, quando o foco do usuário sai e volta, a criança anteriormente focada será refocada.

Uma técnica comum para gerenciamento de foco é "Roving Tab Index", onde o elemento em foco no grupo tem um índice de tabulação de 0 e outros elementos têm um índice de tabulação de -1. Desta forma, quando o usuário retornar ao grupo de foco, o elemento com índice de tabulação 0 terá automaticamente o foco.

Uma primeira implementação do NavMenu pode ser mais ou menos assim:

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 (
    
  );
}

O e.preventDefault() existe para evitar que coisas como ArrowDown rolem a página.

Aqui está o componente MenuItem. Vamos ignorar os itens do submenu apenas por um segundo. Estamos usando useEffect, usePrevious e element.focus() para focar no elemento sempre que focusIndex for alterado:

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}
  • ); }

    Observe que é a tag a que deve ter o ref (botão para item de menu com submenus), então, quando eles estiverem focados, os comportamentos padrão do teclado entrarão em ação conforme o esperado, como a navegação em Enter. Além do mais, o índice da guia está sendo atribuído corretamente dependendo do elemento em foco.

    Estamos adicionando um manipulador de eventos para o evento em foco caso o evento em foco não seja de um evento de tecla/mouse. Aqui está uma citação do documento da web:

    Não presuma que todas as mudanças de foco ocorrerão por meio de eventos de tecla e mouse: tecnologias assistivas, como leitores de tela, podem definir o foco para qualquer elemento focável.

    Ajuste nº 1

    Se você seguir o useEffect descrito acima, descobrirá que o primeiro elemento terá foco mesmo que o usuário não tenha usado o teclado para navegar. Para corrigir isso, podemos verificar o elemento ativo e chamar focus() apenas quando o usuário iniciar algum evento de teclado, o que desvia o foco do corpo.

      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]);
    

    Reutilização lógica e gancho personalizado

    Até agora, temos componentes funcionais NavMenu e MenuItemLink. Vamos passar para o item de menu com submenus.

    À medida que eu o desenvolvia rapidamente, percebi que esse item de menu compartilhará a maior parte da lógica

    Declaração de lançamento Este artigo foi reproduzido em: https://dev.to/godsamit/building-an-accessible-navigation-menubar-with-react-hooks-blh?1 Se houver alguma violação, entre em contato com [email protected] para excluir isto
    Tutorial mais recente Mais>

    Isenção de responsabilidade: Todos os recursos fornecidos são parcialmente provenientes da Internet. Se houver qualquer violação de seus direitos autorais ou outros direitos e interesses, explique os motivos detalhados e forneça prova de direitos autorais ou direitos e interesses e envie-a para o e-mail: [email protected]. Nós cuidaremos disso para você o mais rápido possível.

    Copyright© 2022 湘ICP备2022001581号-3