Maison >interface Web >js tutoriel >Créer une barre de menus de navigation accessible avec React Hooks
Publié accidentellement ! Veuillez revenir plus tard pour en savoir plus !
Créer des applications Web accessibles n'est pas seulement une bonne pratique, c'est désormais une nécessité. Récemment, j'ai eu l'opportunité de créer une barre de menus de navigation axée sur a11y. Au cours de mes recherches, j'ai réalisé à quel point la plupart des barres de menus ne sont pas conformes au modèle ARIA. Par exemple, au lieu de parcourir les éléments de menu, saviez-vous qu'il faut naviguer dans une barre de menus avec les touches fléchées et gérer son propre focus ?
Bien que j'ai trouvé quelques tutoriels, je n'ai finalement pas suivi complètement. J'écris ceci parce que je pense que ce que j'ai fini par construire vaut la peine d'être partagé - si vous avez également une affinité avec les petits composants et les crochets personnalisés.
Bien que je structure ce blog avec quelques étapes de développement, mon objectif n'est pas d'écrire un guide étape par étape. Je vous fais confiance pour connaître les bases de React et le fonctionnement des hooks personnalisés.
Je ne partage pour le moment que les principaux détails de mise en œuvre, mais je prévois de mettre à jour cet article avec un exemple de code sandbox à l'avenir lorsque j'aurai plus de temps.
Pour ce blog, nous envisageons une barre de menus de navigation, comme celles que vous voyez en haut ou sur le côté de nombreuses applications Web. Dans cette barre de menus, certains éléments de menu peuvent avoir des sous-menus, qui s'ouvriront/se fermeront en entrant/sortant la souris.
Tout d'abord, le HTML sémantique et les rôles et attributs ARIA appropriés sont essentiels à l'accessibilité. Pour le modèle de barre de menus, vous pouvez en savoir plus sur la documentation officielle ici.
Voici un exemple de balisage HTML approprié :
<nav aria-label="Accessible Menubar"> <menu role="menubar"> <li role="none"> <a role="menuitem" href="/">Home</a> </li> <li role="none"> <a role="menuitem" href="/about">About</a> </li> <li role="none"> <button role="menuitem" aria-haspopup="true" aria-expanded="false" > Expand Me! </button> <menu role="menu"> <li role="none"> <a role="menuitem" href="/sub-item-1">Sub Menu Item 1</a> </li> <li role="none"> <a role="menuitem" href="/sub-item-2">Sub Menu Item 2</a> </li> </menu> </li> </menu> </nav>
Remarquez que nous utilisons la balise bouton pour le HTML sémantique. Le bouton doit également avoir aria-haspopup pour alerter les lecteurs d'écran. Enfin, l'attribut aria-expanded approprié doit être attribué en fonction de l'état du menu.
Passons en revue les composants dont nous avons besoin. Évidemment, nous avons besoin d'un composant de menu global, ainsi que d'un composant d'élément de menu.
Certains éléments de menu ont un sous-menu tandis que d'autres n'en ont pas. Les éléments de menu avec des sous-menus devront gérer leurs états pour l'ouverture/fermeture du sous-menu lors des événements de survol et de clavier. Il doit donc être son propre composant.
Les sous-menus doivent également être son propre composant. Bien que les sous-menus ne soient également que des conteneurs pour les éléments de menu, ils ne gèrent pas leurs états ni les événements du clavier. Cela les différencie du menu de navigation de niveau supérieur.
J'ai fini par écrire ces composants :
En termes très simples, « gestion de la concentration » signifie simplement que le composant doit savoir quel enfant est concentré. Ainsi, lorsque le focus de l'utilisateur quitte et revient, l'enfant précédemment focalisé sera recentré.
Une technique courante pour la gestion du focus est « Roving Tab Index », où l'élément ciblé dans le groupe a un index de tabulation de 0 et les autres éléments ont un index de tabulation de -1. De cette façon, lorsque l'utilisateur reviendra au groupe de discussion, l'élément avec l'index de tabulation 0 aura automatiquement le focus.
Une première implémentation pour NavMenu peut ressembler à ceci :
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 ( <nav> <menu role="menubar" onKeyDown={handleKeyDown}> {menuItems.map((item, index) => <MenuItem key={item.label} item={item} index={index} focusedIndex={focusedIndex} setFocusedIndex={setFocusedIndex} /> )} </menu> </nav> ); }
Le e.preventDefault() est là pour empêcher des choses comme ArrowDown de faire défiler la page.
Voici le composant MenuItem. Ignorons les éléments du sous-menu juste pendant une seconde. Nous utilisons useEffect, usePrevious et element.focus() pour nous concentrer sur l'élément chaque fois que focusIndex change :
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 ( <li role="none"> <a ref={linkRef} role="menuitem" tabIndex={isFocused ? 0 : -1} onFocus={handleFocus} > {item.label} </a> </li> ); }
Remarquez que c'est la balise a qui devrait avoir la ref (bouton pour l'élément de menu avec sous-menus), donc lorsqu'ils sont ciblés, les comportements par défaut du clavier entreront en vigueur comme prévu, comme la navigation sur Entrée. De plus, l'index de tabulation est correctement attribué en fonction de l'élément ciblé.
Nous ajoutons un gestionnaire d'événements pour l'événement focus au cas où l'événement focus ne proviendrait pas d'un événement clé/souris. Voici une citation du web doc :
Ne présumez pas que tous les changements de focus se produiront via des événements de touche et de souris : les technologies d'assistance telles que les lecteurs d'écran peuvent définir le focus sur n'importe quel élément pouvant être focalisé.
Si vous suivez l'useEffect décrit ci-dessus, vous constaterez que le premier élément aura le focus même si l'utilisateur n'a pas utilisé le clavier pour naviguer. Pour résoudre ce problème, nous pouvons vérifier l'élément actif et appeler focus() uniquement lorsque l'utilisateur a démarré un événement clavier, ce qui détourne le focus du corps.
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]);
So far, we have functional NavMenu and MenuItemLink components. Let's move on to menu item with sub menus.
As I was quickly building it out, I realized that this menu item will share the majority of the logic
Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!