¡Publicado accidentalmente! ¡Vuelve más tarde para obtener más información!
Crear aplicaciones web accesibles no es solo una buena práctica: ahora es una necesidad. Recientemente, tuve la oportunidad de crear una barra de menú de navegación centrada en todos. Mientras investigaba, me di cuenta de que la mayoría de las barras de menú que existen no cumplen con el patrón ARIA. Por ejemplo, en lugar de desplazarse por los elementos del menú con tabulaciones, ¿sabía que se debe navegar por una barra de menú con las teclas de flecha y administrar su propio enfoque?
Aunque encontré algunos tutoriales, terminé por no seguirlos por completo. Escribo esto porque creo que vale la pena compartir lo que terminé construyendo, si también tienes afinidad con los componentes pequeños y los ganchos personalizados.
Aunque estructuraré este blog con algunos pasos de desarrollo, mi objetivo no es escribir una guía paso a paso. Confío en que conozcas los conceptos básicos de React y cómo funcionan los ganchos personalizados.
Por ahora solo compartiré los detalles clave de implementación, pero planeo actualizar este artículo con un ejemplo de código de zona de pruebas en el futuro, cuando tenga más tiempo.
Para este blog, estamos construyendo una barra de menú de navegación, como las que ves en la parte superior o lateral de muchas aplicaciones web. En esta barra de menú, algunos elementos del menú pueden tener submenús, que se abrirán/cerrarán al entrar/salir del mouse.
En primer lugar, el HTML semántico y los roles y atributos ARIA apropiados son esenciales para la accesibilidad. Para conocer el patrón de la barra de menú, puede leer más en el documento oficial aquí.
Aquí hay un ejemplo de marcado HTML apropiado:
Observe que estamos usando la etiqueta de botón para HTML semántico. El botón también debería tener aria-haspopup para alertar a los lectores de pantalla. Por último, se debe asignar el atributo aria-expanded apropiado según el estado del menú.
Repasemos los componentes que necesitamos. Obviamente, necesitamos un componente de menú general, así como un componente de elemento de menú.
Algunos elementos del menú tienen un submenú mientras que otros no. Los elementos del menú con submenús deberán administrar sus estados para abrir/cerrar el submenú al pasar el mouse y en eventos de teclado. Por lo tanto, debe ser su propio componente.
Los submenús también deben ser su propio componente. Aunque los submenús también son solo contenedores para elementos de menú, no administran sus estados ni manejan eventos de teclado. Esto los diferencia del menú de navegación de nivel superior.
Terminé escribiendo estos componentes:
En palabras muy sencillas, "gestión del enfoque" simplemente significa que el componente necesita saber qué niño tiene el enfoque. Entonces, cuando el enfoque del usuario se va y regresa, el niño previamente enfocado será reenfocado.
Una técnica común para la gestión del enfoque es el "índice de pestaña móvil", donde el elemento enfocado en el grupo tiene un índice de pestaña de 0 y otros elementos tienen un índice de pestaña de -1. De esta manera, cuando el usuario regrese al grupo de enfoque, el elemento con índice de pestaña 0 automáticamente tendrá el foco.
Una primera implementación de NavMenu puede verse así:
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 ( ); }
El e.preventDefault() está ahí para evitar que cosas como ArrowDown se desplacen por la página.
Aquí está el componente MenuItem. Ignoremos los elementos con submenú sólo por un segundo. Estamos usando useEffect, usePrevious y element.focus() para centrarnos en el elemento cada vez que focusIndex cambia:
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 (
Observe que es la etiqueta a la que debe tener la referencia (botón para elemento de menú con submenús), de modo que cuando estén enfocados, los comportamientos predeterminados del teclado se activarán como se esperaba, como la navegación con Enter. Además, el índice de la pestaña se asigna correctamente según el elemento enfocado.
Estamos agregando un controlador de eventos para el evento de enfoque en caso de que el evento de enfoque no provenga de un evento de tecla o mouse. Aquí hay una cita del documento web:
No asuma que todos los cambios de enfoque se producirán mediante eventos de teclas y mouse: las tecnologías de asistencia, como los lectores de pantalla, pueden establecer el enfoque en cualquier elemento enfocable.
Si sigues el useEffect descrito anteriormente, encontrarás que el primer elemento tendrá el foco incluso si el usuario no ha usado el teclado para navegar. Para solucionar este problema, podemos verificar el elemento activo y solo llamar a focus() cuando el usuario haya iniciado algún evento de teclado, lo que desvía el foco del cuerpo.
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]);
Hasta ahora, tenemos componentes funcionales NavMenu y MenuItemLink. Pasemos al elemento del menú con submenús.
Mientras lo estaba construyendo rápidamente, me di cuenta de que este elemento del menú compartirá la mayor parte de la lógica
Descargo de responsabilidad: Todos los recursos proporcionados provienen en parte de Internet. Si existe alguna infracción de sus derechos de autor u otros derechos e intereses, explique los motivos detallados y proporcione pruebas de los derechos de autor o derechos e intereses y luego envíelos al correo electrónico: [email protected]. Lo manejaremos por usted lo antes posible.
Copyright© 2022 湘ICP备2022001581号-3