wandres.dev
ELEMENTS IV · Accesibilidad en el inspector

Propiedades ARIA computadas: lo implícito frente a lo declarado

Cómo leer la lista de propiedades computadas del panel, la diferencia entre lo que declaras y lo que el navegador deduce, y los conflictos que produce sobrescribir semántica nativa.

⏱ 15 min

La pestaña de accesibilidad muestra dos listas que parecen la misma y no lo son: los atributos ARIA que has escrito tú, y las propiedades computadas que el navegador ha deducido combinando el elemento, sus atributos, su posición en el árbol y sus estados. Leer la segunda es lo que revela los conflictos, porque un elemento puede tener declarado algo que el navegador está ignorando por completo, y esa discrepancia no produce ningún error visible en ningún sitio.

🎯 Al terminar esta lección sabrás
  • Distinguir atributos ARIA declarados de propiedades computadas y saber cuál manda.
  • Identificar los valores implícitos que un elemento nativo aporta sin que los escribas.
  • Detectar un rol declarado que entra en conflicto con la semántica nativa del elemento.
  • Diagnosticar propiedades ARIA ignoradas por no ser aplicables al rol.

Las dos listas

La sección de atributos ARIA lista literalmente los atributos que empiezan por aria- presentes en el elemento, con su valor tal cual está escrito. Es una vista del marcado.

La sección de propiedades computadas lista el resultado del cálculo: el rol efectivo, el nombre, la descripción, y el conjunto de estados y propiedades que el navegador expone a la API de accesibilidad. Aquí aparecen cosas que no has escrito.

Un input de tipo checkbox marcado tiene checked: true en propiedades computadas sin que hayas escrito ningún aria-checked. Un button con el atributo disabled tiene disabled: true sin aria-disabled. Un elemento dentro de una lista tiene su posición en el conjunto sin que hayas escrito aria-posinset. Todo eso son valores implícitos que el elemento nativo aporta.

La comparación entre ambas listas es el diagnóstico. Si has declarado algo y no aparece en las computadas, no está teniendo efecto.

ℹ️
Nota

Que un atributo ARIA no aparezca en las propiedades computadas no siempre es un error: muchas propiedades solo se exponen cuando tienen un valor distinto del predeterminado. Lo que sí es señal es que aparezca con un valor distinto del que declaraste, o que aparezca un rol distinto del que pusiste.

Los valores implícitos de los elementos nativos

Merece la pena tener presente qué trae cada elemento de fábrica, porque explica por qué el HTML semántico ahorra tanto trabajo.

Elemento Rol implícito Estados y propiedades implícitos
button button disabled desde el atributo
a con href link
input type="checkbox" checkbox checked, disabled, required
input type="radio" radio checked, posición en el grupo
input type="text" textbox required, invalid, readonly, multiline a falso
select combobox o listbox expanded, multiselectable
nav navigation
main main
h1 a h6 heading nivel del encabezado
ul y ol list número de elementos
li listitem posición y tamaño del conjunto
table table filas, columnas, posiciones
dialog con showModal dialog modal a verdadero
details y summary group y button expanded

La columna de la derecha es la que se pierde al reimplementar con div. Un li sabe que es el tercero de siete y lo expone; un div con role="listitem" dentro de un div con role="list" también lo hace, pero solo si el navegador puede contar, lo que exige que la relación padre-hijo sea directa, cosa que los frameworks rompen a menudo con envoltorios.

Conflictos entre rol declarado y semántica nativa

Poner un role en un elemento que ya tiene semántica lo sustituye, y eso rara vez es lo que se quiere. Los tres casos típicos.

role redundante. Escribir role="button" en un button no hace daño pero tampoco aporta, y ensucia. La única excepción legítima es cuando se quiere señalar la intención en un código donde el elemento podría cambiar.

role que degrada. Poner role="presentation" en un elemento con significado lo elimina del árbol semántico. En una table de layout es correcto; en cualquier otro sitio, casi nunca. Y hay una regla que sorprende: los roles de presentación no se aplican a elementos que puedan recibir foco; un button con role="presentation" sigue exponiéndose como botón, porque lo contrario dejaría un elemento enfocable sin identidad.

role que contradice. Un a con role="button" es el caso más discutido. Puede ser correcto cuando el enlace de verdad se comporta como botón y no navega, y es un error cuando sí navega, porque el usuario espera de un botón que actúe en la página y de un enlace que lo lleve a otro sitio. La pista para decidir es simple: si al pulsarlo cambia la URL, es un enlace.

Las propiedades computadas muestran el rol efectivo, así que comprobar cualquiera de los tres casos es un vistazo.

Propiedades ignoradas por no aplicar al rol

La otra fuente de silencio. Cada propiedad ARIA está definida para un conjunto de roles, y aplicarla a un rol que no la admite hace que se ignore. Los casos más frecuentes.

aria-expanded en un elemento sin rol interactivo. No aparece en las computadas.

aria-selected fuera de una option, una pestaña, una fila de rejilla o un elemento de árbol. Es de los más mal usados: mucha gente lo pone en botones de filtro, donde el correcto es aria-pressed.

aria-required en un elemento que no es un control de entrada.

aria-level fuera de encabezados, elementos de árbol y de lista.

Y el más importante de todos por sus consecuencias: los roles interactivos exigen que el elemento sea enfocable. Un div con role="button" sin tabindex="0" aparece en el árbol como botón y es inalcanzable con teclado. Las propiedades computadas incluyen si el elemento es enfocable, y esa fila es la que hay que mirar siempre que se declare un rol interactivo sobre un elemento genérico.

// Elementos con rol interactivo declarado que no son alcanzables con teclado
const rolesInteractivos = ['button', 'link', 'checkbox', 'radio', 'tab', 'menuitem', 'switch', 'option'];
console.table(
  $$('[role]')
    .filter(el => rolesInteractivos.includes(el.getAttribute('role')))
    .filter(el => !el.hasAttribute('tabindex') && !el.matches('a[href], button, input, select, textarea'))
    .map(el => ({ rol: el.getAttribute('role'), html: el.outerHTML.slice(0, 70) }))
);

Cualquier fila que devuelva ese fragmento es un control que existe para el lector de pantalla y no existe para el teclado, que es una de las formas más frustrantes de romper una interfaz.

ARIA no cambia el comportamiento, solo la etiqueta

Hay un modelo mental erróneo que explica la mayoría del ARIA mal usado que se ve en producción, y merece enunciarse sin rodeos: los atributos ARIA no hacen absolutamente nada. No añaden comportamiento, no capturan eventos de teclado, no gestionan el foco, no modifican el estilo, no cambian el orden de tabulación. Lo único que hacen es alterar la información que el navegador envía a la API de accesibilidad del sistema. Son etiquetas descriptivas, y como cualquier etiqueta descriptiva, pueden mentir. Poner role="button" en un div no lo convierte en un botón: lo convierte en un div que dice ser un botón, y el usuario de lector de pantalla recibe la promesa de que puede pulsarlo con la barra espaciadora, promesa que no se cumple hasta que alguien escriba el manejador. Y ahí está lo perverso: un componente sin ARIA está roto de forma detectable, y un componente con ARIA incorrecto está roto de forma engañosa. Un div con manejador de clic y sin ARIA es invisible para el lector de pantalla, lo que es un problema evidente que cualquier auditoría encuentra. El mismo div con role="button" y aria-pressed pasa la auditoría automática, aparece correctamente en el panel, y sigue sin responder al teclado. Ese segundo caso es peor porque nadie lo va a encontrar hasta que un usuario real se atasque. La comprobación que separa una cosa de la otra no está en ninguna pestaña de las DevTools y hay que hacerla a mano: navega tu componente solo con el teclado, con las manos fuera del ratón. Tabulador, entrada, espacio, flechas, escape. Si algo que se anuncia como botón no responde al espacio, o algo que se anuncia como menú no responde a las flechas, tu ARIA está mintiendo, y el panel no te lo va a decir porque el panel comprueba lo que declaras, no lo que ocurre.