ARIA mal: Casi ningún menú debería llevar role="menu" (y tu carrusel tampoco listbox)

Por Iván Novegil
En ocasiones, el nombre de los roles ARIA puede llevar a confusión acerca de su propósito. Quizá el caso más conocido es el del rol presentation; se trata de un rol que elimina la semántica nativa del elemento sobre el que se aplica, pero en ARIA 1.1 se introdujo un rol equivalente, denominado none, porque, según la nueva especificación, muchos desarrolladores entendían el rol presentation como un rol para ocultar el contenido a las tecnologías de asistencia, equivalente al atributo aria-hidden="true", cuando presentation realmente solo elimina la semántica.
Hoy vamos a hablar de dos confusiones muy habituales a las que el borrador de la próxima versión de ARIA aún no ha dado solución, al menos de momento, el rol menu en cualquier menú y el rol listbox en los carruseles.
Antes de empezar: Este artículo ya estaba escrito desde antes, pero se ha dado la casualidad de que alguien ha tenido la imprudencia de darnos una charla en la A11yConf 2026. Casualmente, sobre ARIA mal utilizado y sobre algunos componentes nativos que nos pueden hacer la vida más fácil y con menos ARIA (sí, menos ARIA, sacrilegio). Será en Zaragoza el 28 de noviembre y en fin, que si te ha gustado este artículo y te interesan también las otras charlas, puedes comprar tu entrada. Prometemos criticar el rol menu, igual que en este artículo.
El rol menu en todos los menús
Hace un tiempo publiqué en redes: "Algún día voy a hacer un post sobre el rol menu. Y pondré bien grande en mayúsculas: CASI NINGÚN MENÚ DEBE LLEVAR ROL MENU. Grabáoslo a fuego".
Pues hoy es ese día, y repito: CASI NINGÚN MENÚ DEBE LLEVAR ROL MENU.
No porque usar el rol menu no sea bueno o correcto, o porque no me guste o yo lo diga, sino porque la amplísima mayoría de las implementaciones de este rol son incorrectas, descontando la honrosa excepción de la mayoría de los menús tipo dropdown accionados mediante un botón (por ejemplo, un menú de acciones con el típico botón de los tres puntos para abrirlo), que sí suelen estar bien implementados, aunque hay de todo.
Pongamos el caso del menú de navegación de esta web. Una vez hecho, me entra la fiebre de la accesibilidad (bien, punto para mí, porque peor sería si lo hubiera dejado todo hecho un asco) y descubro que ARIA tiene un rol menu,. Como esto es un menú, voy y lo añado.
Ver código
<ul class="nav-menu" role="menu">
<li role="presentation"><a href="/" role="menuitem">Inicio</a></li>
<li role="presentation"><a href="/blog/" aria-current="true" role="menuitem">Blog</a></li>
<li role="presentation"><a href="/quienes-somos/" role="menuitem">Quiénes somos</a></li>
<li role="presentation"><a href="/participaciones/" role="menuitem">Charlas y ponencias</a></li>
<li role="presentation"><a href="/contacto/" role="menuitem">Contacto</a></li>
</ul>
Pero se me ha pasado leer bien la descripción del rol:
Especificación ARIA, "menu role": El rol
menues apropiado cuando una lista de elementos de menú se presenta de manera similar a un menú de una aplicación de escritorio. Para que sea accesible por teclado, los autores deberían gestionar el foco de los descendientes para todas las instancias de este rol{...}.
Leyendo esto, vemos que realmente el concepto detrás del rol menu es un menú como los del sistema operativo o las aplicaciones de escritorio, es decir, un menú que se navega utilizando las flechas, no el tabulador. El ejemplo anterior, dado que está construido con enlaces a los que se les ha aplicado un atributo role, de manera que todos ellos están por defecto en el orden de tabulación, funciona justo al revés, se puede navegar con el tabulador, pero no con las flechas.
Es importante tener en cuenta que los roles, por lo general, crean tanto en las tecnologías de asistencia como en sus usuarios una expectativa concreta sobre el comportamiento del componente al que se aplican. Por ejemplo, si le doy a un enlace a otra página rol textbox (os prometo que esto lo he visto en una web real) un lector de pantalla permitirá que el usuario desactive, o desactivará automáticamente en muchos casos, su capa de atajos de teclado personalizados para que el usuario pueda escribir en ese supuesto campo de texto. Pero no hay ningún campo de texto, no se puede escribir ni navegar por él y el usuario puede no tener ni idea de que eso realmente es un botón. Sucede lo mismo con el rol menu, estamos informando al usuario de que la interacción implementada para ese componente es la navegación utilizando las flechas, pero realmente solo hemos añadido el rol sin implementar esa interacción de teclado, por lo que sería como quedarnos a medio camino entre un menú normal y un menú con rol menu real. Por ejemplo, en el lector de pantalla NVDA, cuando utilizando el tabulador el usuario llega a un componente con rol menu, se desactiva automáticamente la capa de atajos de teclado del lector para que el usuario pueda interactuar directamente con el componente utilizando las flechas.
En casi todas las ocasiones en las que identificamos esta barrera la recomendación es directamente quitar el rol menu y otros roles que se pudieran haber añadido relacionados con menu (menuitem, presentation/none en los li, etc.). Un menú presentado en una lista HTML normal (ul), por ejemplo un menú de navegación, es perfectamente normal y de hecho muchas veces incluso más aconsejable que un menú con rol menu, aunque esté perfectamente implementado, ya que al usar menu necesitamos introducir código para gestionar el foco a través de eventos de teclado vinculados a las teclas de flecha, y obviamente esto es más complicado de mantener a futuro que una simple lista HTML. En fin, que creo que hace ya un rato que he perdido el positivo que me había autoconcedido más arriba por haberme acordado de la accesibilidad, porque en este caso si hubiera dejado el menú como estaba sería perfectamente accesible y no quedaría para nada hecho un asco.
Si aún así queremos usar el rol menu, aparte de añadir menu, menuitem y cualquier otro rol necesario, tendremos que gestionar el foco, ya sea moviéndolo manualmente, ya sea utilizando el atributo aria-activedescendant, de manera que los distintos elementos del menú reciban foco secuencialmente al pulsar las flechas arriba/abajo. Si nuestro menú tiene orientación horizontal y utilizamos las flechas izquierda/derecha, debemos añadir al contenedor con role="menu" el atributo aria-orientation="horizontal", ya que el valor por defecto es vertical. Este ejemplo de la guía de prácticas de autoría de ARIA, si bien sobrepasa el ámbito de lo que estamos explicando porque también aborda el rol menubar, ilustra bien cómo debería gestionarse el foco en un contenedor con role="menu".
El rol listbox en los carruseles
Esta es una maldición que alguien en algún momento le echó a la accesibilidad y aún hoy no nos hemos librado del todo de ella. Un poco como la maldición de que supuestamente los textos alternativos tienen un límite de 125 caracteres, que otro día habrá que hablar de ella también. Y, de nuevo, es un caso en que interesarse por la accesibilidad, con toda la buena intención, sin duda, no equivale a hacer las cosas bien necesariamente.
Aparentemente, alrededor de 2010 algunas bibliotecas de componentes empezaron a implementar el rol listbox en los carruseles. Y, como sucede a veces, algunos usuarios de internet se quedaron solo con la copla del rol listbox, y por supuesto la compartieron, llegando a conseguir que este hilo de StackOverflow, en el que la respuesta más votada, de 2013, recomienda usar el rol listbox, sea aún hoy el primer resultado en Google al buscar términos como "carousel ARIA role".
Esta respuesta, que básicamente dice que debería usarse role="listbox", tiene dos problemas principales. Por una parte, listbox no es el rol más recomendable para un carrusel, ni siquiera funcionará bien en la mayoría de carruseles, y por otra, al igual que pasa con el rol menu, es muy fácil dejarse la lógica de teclado por el camino. Si bien la documentación de soporte que se enlaza en la respuesta explica una lógica de teclado razonable, la propia respuesta no advierte que también se debe implementar y hay muchos carruseles, todavía hoy, que tienen rol listbox sin la lógica de teclado necesaria. Basta con tabular un poco por este carrusel para verlo:
Este es un carrusel hiper accesible
Te recuerdo que hablaremos de esta y de más cosas relacionadas con el uso de ARIA en nuestra ponencia en la A11yConf 2026. Compra ya tu entrada
Más turra con nuestra ponencia
Para más información, puedes consultar la descripción de la ponencia.
¿Aún no has comprado tu entrada?
Entiendo que pienses que nuestra charla es una pérdida de tiempo, pero compra tu entrada igualmente para poder ir a las otras. Y de propina, puedes escoger uno de los cuatro talleres de la jornada. consulta aquí el programa
Ver código
<div data-carrusel style="max-width:640px;margin:auto" role="region" aria-label="Carrusel de diapositivas">
<button type="button" data-anterior>Anterior</button>
<div style="overflow:clip;border:2px solid #333">
<ul data-diapositivas role="listbox" style="display:flex;width:100%;margin:0;padding:0;list-style:none;transform:translateX(0);transition:transform 300ms ease">
<li role="option" style="flex:0 0 100%;min-width:0;box-sizing:border-box;min-height:200px;padding:24px;background:#e8f0ff"><h3>Este es un carrusel hiper accesible</h3><p>Te recuerdo que hablaremos de esta y de más cosas relacionadas con el uso de ARIA en nuestra ponencia en la A11yConf 2026. <a href="https://tickets.a11yconf.com">Compra ya tu entrada</a></p></li>
<li role="option" style="flex:0 0 100%;min-width:0;box-sizing:border-box;min-height:200px;padding:24px;background:#e8ffe8"><h3>Más turra con nuestra ponencia</h3><p>Para más información, puedes consultar <a href="https://2026.a11yconf.com/agenda/aria-por-si-acaso">la descripción de la ponencia</a>.</p></li>
<li role="option" style="flex:0 0 100%;min-width:0;box-sizing:border-box;min-height:200px;padding:24px;background:#fff0dc"><h3>¿Aún no has comprado tu entrada?</h3><p>Entiendo que pienses que nuestra charla es una pérdida de tiempo, pero <a href="https://tickets.a11yconf.com">compra tu entrada</a> igualmente para poder ir a las otras. Y de propina, puedes escoger uno de los cuatro talleres de la jornada. <a href="https://2026.a11yconf.com/agenda">consulta aquí el programa</a></p></li>
</ul>
</div>
<button type="button" data-siguiente>Siguiente</button>
<script>
(()=>{
const c=document.currentScript.parentElement,l=c.querySelector("[data-diapositivas]");
let i=0;
function mostrar(n) {
if(n<0||n>=l.children.length) return;
i=n;
l.style.transform=`translateX(-${i*100}%)`;
}
c.querySelector("[data-anterior]").addEventListener("click",()=>mostrar(i-1));
c.querySelector("[data-siguiente]").addEventListener("click",()=>mostrar(i+1));
})();
</script>
</div>
¿Por qué listbox no funcionará bien en la mayoría de carruseles? En la especificación ARIA, "listbox role" se indica de forma algo ambigua que el rol está pensado para controles similares a un elemento select, que es el típico desplegable HTML usado normalmente en formularios. Un carrusel no se trata de un elemento de formulario, por lo que estaríamos utilizando con propósitos de navegación un control pensado en realidad para la entrada de datos.
Además, tendremos un fantástico problema con el foco y la semántica dentro de las diapositivas. Por una parte, nuestra lógica de teclado debe tener en cuenta que una diapositiva de carrusel puede contener elementos como enlaces, que deben recibir el foco utilizando el tabulador, y si los listbox no estaban pensados para funcionar como elementos de navegación, mucho menos para contener elementos interactivos. Y por otra, a partir de ARIA 1.1, publicada en 2017, ya se indica expresamente que cualquier hijo de los nodos con las opciones de los listbox, que deben llevar rol option, es presentacional, es decir, que se eliminará la semántica de cualquiera de los elementos contenidos en las diapositivas y solamente se presentará el texto plano a las tecnologías de asistencia. Esto, en la práctica, no se aplicará a los elementos interactivos (enlaces, botones, etc.) debido a una de las maravillosas interioridades de las reglas sobre contenido presentacional de ARIA, pero sí implicará que cualquier tipo de semántica, como por ejemplo un encabezado o una lista dentro de las diapositivas, sea totalmente inútil para cualquier tecnología de asistencia, ya que percibirán la diapositiva como conjunto. Esto tampoco es realmente así actualmente al menos en NVDA, porque los encabezados del ejemplo de arriba se leen perfectamente, pero la especificación es clara al respecto y esto puede cambiar en cualquier momento.
Y esto nos deja en un caso esencialmente similar al de menu. ¿Para qué mantener una lógica de teclado personalizada, que por tanto habrá que mantener, y con unos roles que no están pensados para eso, si encima estamos degradando la semántica? De nuevo, la recomendación suele ser siempre eliminar directamente estos roles y hacer un carrusel con un elemento section y aria-live, al estilo tradicional pero no por ello menos efectivo. Pronto hablaremos sobre buenas prácticas para crear carruseles porque además es un tema que tiene bastante tela, más allá del listbox que cada vez se ve menos.
¿Conocías ya estos ejemplos, o se te ocurre alguno distinto? Te leemos en los comentarios.