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 hooks 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 que les principaux détails de mise en œuvre pour le moment, mais je prévois de mettre à jour cet article avec un exemple de bac à sable de code à 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 appuyant/quittant 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é :
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, d'autres non. 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 s'agir d'un composant à part entière.
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 revient 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 ( ); }
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 (
Notez 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 du clavier par défaut 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 document Web :
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]);
Jusqu'à présent, nous avons des composants NavMenu et MenuItemLink fonctionnels. Passons à l'élément de menu avec les sous-menus.
Au fur et à mesure que je le construisais rapidement, j'ai réalisé que cet élément de menu partagerait la majorité de la logique
Clause de non-responsabilité: Toutes les ressources fournies proviennent en partie d'Internet. En cas de violation de vos droits d'auteur ou d'autres droits et intérêts, veuillez expliquer les raisons détaillées et fournir une preuve du droit d'auteur ou des droits et intérêts, puis l'envoyer à l'adresse e-mail : [email protected]. Nous nous en occuperons pour vous dans les plus brefs délais.
Copyright© 2022 湘ICP备2022001581号-3