El árbol de accesibilidad: el otro documento que el navegador construye
Qué es el árbol de accesibilidad, cómo se deriva del DOM, por qué la mitad de tus nodos no aparecen en él, y dónde se inspecciona.
El navegador no construye un árbol, construye dos. Uno es el DOM, que gobierna el layout y la pintura. El otro es el árbol de accesibilidad, que es lo que un lector de pantalla, un conmutador o un sistema de control por voz reciben del navegador a través de la API de accesibilidad del sistema operativo. Se derivan del mismo marcado y no son iguales: hay nodos del DOM que no existen en el árbol de accesibilidad, hay nodos del árbol de accesibilidad que no corresponden a ningún elemento, y hay elementos cuyo significado cambia por completo entre uno y otro.
- Explicar cómo se deriva el árbol de accesibilidad del DOM y qué transformaciones sufre.
- Inspeccionar el árbol completo y el de un nodo concreto desde el panel de Elements.
- Enumerar las razones por las que un nodo queda excluido del árbol y detectarlas.
- Distinguir un problema de estructura de accesibilidad de uno de etiquetado.
Cómo se construye
El proceso tiene cuatro pasos y conocerlos explica casi todos los comportamientos que sorprenden.
Se parte del árbol de renderizado, no del DOM completo. Los nodos que no generan caja porque tienen display: none no llegan al árbol de accesibilidad. Los que están ocultos con visibility: hidden tampoco. Pero los que están fuera de pantalla con posicionamiento, o los que tienen opacity: 0, o los que están tapados por otro elemento, sí llegan: son invisibles para el ojo y perfectamente presentes para un lector de pantalla.
A cada nodo se le calcula un rol. Puede venir del elemento —button tiene rol de botón, nav de navegación— o de un atributo role explícito que sustituye al implícito. Los elementos sin semántica, como div y span, tienen rol genérico.
A cada nodo se le calcula un nombre accesible y una descripción, siguiendo un algoritmo especificado que tiene un orden de precedencia estricto y que es el tema de la lección siguiente.
Se aplican reglas de poda. El árbol resultante se limpia: se eliminan nodos sin significado, se colapsan cadenas de contenedores genéricos, y se excluyen los que estén marcados como ocultos para la accesibilidad. El resultado es un árbol notablemente más pequeño que el DOM, que es exactamente el objetivo: un lector de pantalla que anunciara los cuarenta div de envoltorio sería inutilizable.
La palabra clave para entender la diferencia entre ambos árboles es semántica. El DOM describe estructura y presentación; el árbol de accesibilidad describe significado e interacción. Un div con un manejador de clic es un elemento perfectamente funcional en el DOM y un contenedor genérico sin ninguna interactividad en el árbol de accesibilidad. Esa asimetría es el origen de la mayoría de los problemas de accesibilidad de las aplicaciones modernas.
Dónde se inspecciona
El panel de Elements tiene una pestaña lateral de accesibilidad, junto a estilos, computados y layout. Muestra cuatro secciones para el nodo seleccionado: su posición en el árbol de accesibilidad, sus atributos ARIA, sus propiedades computadas, y el orden de fuente.
Además hay un modo de árbol completo, que sustituye el árbol del DOM en el panel de Elements por el árbol de accesibilidad de toda la página. Se activa desde la propia pestaña de accesibilidad o desde el menú de comandos buscando el término. Ese modo es el que hay que usar para auditar, porque enseña la estructura tal como la percibe la tecnología asistiva, con los roles y los nombres en lugar de las etiquetas y las clases.
La primera vez que se activa sobre una aplicación real, la experiencia suele ser instructiva. Páginas que parecen bien estructuradas resultan ser una lista plana de treinta elementos genéricos sin ninguna jerarquía. Y esa vista plana es literalmente lo que un usuario de lector de pantalla tiene que recorrer.
Desde la consola hay dos utilidades que dan el mismo dato de forma programática.
// Nombre y rol accesibles de todos los controles interactivos de la pagina
(async () => {
const nodos = $$('button, a, input, select, textarea, [role]');
const filas = [];
for (const n of nodos) {
filas.push({
etiqueta: n.tagName.toLowerCase(),
rol: await getAccessibleRole(n),
nombre: await getAccessibleName(n)
});
}
console.table(filas);
})();
Las funciones getAccessibleName y getAccessibleRole son utilidades de la consola de las DevTools —no existen en el lenguaje— y devuelven promesas. Cualquier fila de esa tabla con el nombre vacío es un control que un lector de pantalla anunciará sin decir qué es, y eso es un fallo grave.
Por qué un nodo no aparece en el árbol
Seis causas, y distinguirlas importa porque tres son correctas y tres son bugs.
display: none o visibility: hidden. Correcto: el elemento está oculto para todos por igual.
El atributo hidden. Equivale a display: none y es correcto.
aria-hidden="true". Excluye el nodo y todo su subárbol del árbol de accesibilidad manteniéndolo visible. Es correcto para elementos puramente decorativos, y es un bug grave si contiene algo interactivo: un botón dentro de un subárbol con aria-hidden sigue siendo enfocable con el tabulador pero no existe para el lector de pantalla, lo que produce un foco que aterriza en la nada.
El nodo es un contenedor genérico sin contenido significativo. Correcto: es la poda funcionando.
El nodo está dentro de un elemento con role="presentation" o role="none". Estos roles eliminan la semántica del elemento sin eliminar su contenido. Es correcto en el caso clásico de una tabla usada para layout, y un bug cuando se aplica a un elemento que sí tenía significado.
El nodo es inert. El atributo inert desactiva la interacción y la accesibilidad de un subárbol entero, y es la forma correcta de gestionar el contenido de fondo cuando hay un modal abierto. Un inert olvidado es la causa de “no puedo hacer clic en nada y no hay ningún overlay”.
Estructura frente a etiquetado
La distinción que ordena todo el trabajo de accesibilidad en el inspector, y que decide qué mirar primero.
Un problema de etiquetado es que un elemento existe en el árbol con el rol correcto pero sin nombre, o con un nombre inútil como “botón” o “enlace”. Se detecta mirando la columna de nombre y se arregla añadiendo texto, aria-label o aria-labelledby.
Un problema de estructura es que el árbol no refleja la organización de la página: no hay encabezados, o los hay pero saltan niveles, o no hay puntos de referencia, o los elementos interactivos no tienen rol interactivo. Se detecta mirando la forma del árbol completo y es mucho más caro de arreglar, porque suele implicar cambiar marcado.
El orden correcto de auditoría es estructura primero. Un botón bien etiquetado dentro de una página sin encabezados ni puntos de referencia sigue siendo inencontrable.
Hay un matiz que separa a quien ha leído sobre accesibilidad de quien ha probado con un lector de pantalla real, y conviene tenerlo claro antes de sacar conclusiones del panel. El navegador expone el árbol de accesibilidad a través de la API del sistema operativo; lo que el usuario oye lo decide el lector de pantalla, y cada uno decide distinto. El mismo árbol produce anuncios diferentes en VoiceOver, en NVDA y en JAWS, con distinto orden, distinta verbosidad y distintas heurísticas propias que a veces contradicen la especificación. Hay patrones de ARIA perfectamente válidos que un lector concreto ignora, y hay elementos sin ninguna marca especial que un lector anuncia de forma útil porque tiene una regla interna para ese caso. La consecuencia práctica es doble y va en las dos direcciones. Por un lado, el panel es necesario pero no suficiente: un árbol correcto es condición para una buena experiencia y no la garantiza, así que las auditorías serias incluyen probar con al menos un lector real, idealmente el más usado en la plataforma de tu público. Por otro lado, y esto es lo que la gente hace mal, no conviene diseñar contra el comportamiento de un lector concreto: si añades marcado raro porque en tu VoiceOver suena mejor, casi seguro estás rompiendo la experiencia en NVDA. El criterio correcto es escribir marcado semánticamente honesto, verificar en el árbol que el significado se transmite, y usar el lector real para detectar problemas graves, no para afinar la redacción. Y una regla que resume todo lo anterior con una frase que se puede aplicar en cualquier revisión de código: si el árbol de accesibilidad de tu componente necesita cinco atributos ARIA para explicar lo que hace, probablemente el elemento HTML equivocado está en la raíz.