title, desc y aria-labelledby
Los dos elementos que SVG trae para el nombre y la descripción, por qué su soporte solo no basta, y el patrón que funciona de verdad en los lectores de pantalla actuales.
title y desc
title da el nombre accesible de su elemento padre. desc da la descripción. Ambos son elementos, no atributos, y ambos pueden aparecer en cualquier elemento contenedor: el svg raíz, un g, o una forma individual.
<svg viewBox="0 0 24 24" width="24" height="24"> <title>Descargar el informe</title> <path d="M12 3v12m0 0 4-4m-4 4-4-4M4 19h16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" /></svg>Dos reglas de colocación que hay que respetar:
title tiene que ser el primer hijo de su padre. La especificación lo exige, y aunque los motores actuales toleran que esté en otro sitio, hay lectores de pantalla que no. Ponerlo el primero no cuesta nada.
Solo el primer title de un elemento cuenta. Un segundo title hermano se ignora.
Hay un efecto secundario que sorprende: los navegadores muestran el contenido de title como tooltip nativo al pasar el ratón, igual que el atributo title de HTML. Eso puede ser deseable o no. Si no lo quieres (por ejemplo, porque ya tienes un tooltip propio y aparecen dos), la alternativa es usar aria-label en lugar de title.
Por qué el patrón necesita ARIA
En teoría, un svg con un title debería exponer ese texto como nombre accesible sin más. En la práctica, el mapeo de accesibilidad de SVG llegó tarde a los navegadores y el soporte de los lectores de pantalla ha sido irregular durante años. Todavía hoy hay combinaciones donde un title suelto no se anuncia.
El patrón robusto añade tres cosas:
<svg viewBox="0 0 100 100" width="100" height="100" role="img" aria-labelledby="t1 d1"> <title id="t1">Distribución del gasto por categoría</title> <desc id="d1">Un gráfico circular con cuatro sectores. Vivienda 42 por ciento, alimentación 24, transporte 19 y ocio 15.</desc> <!-- las formas --></svg>Qué aporta cada pieza:
role="img" declara que el SVG es una imagen. Sin él, el rol implícito es graphics-document, cuyo soporte en los lectores de pantalla es limitado, y algunos motores exponen el SVG con un rol genérico que no se anuncia como imagen. Con role="img", además, el subárbol se trata como opaco: la tecnología de asistencia no recorre las formas de dentro anunciando basura.
aria-labelledby conecta explícitamente el nombre con el title. Es la vía que mejor soporte tiene, por delante del mapeo implícito.
Los id son necesarios para esa conexión, y arrastran el problema de ámbito global del nivel 20: dos gráficos en la misma página con id="t1" colisionan, y el segundo apuntará al título del primero. Los identificadores de accesibilidad tienen que ser únicos por instancia, generados desde el código.
aria-label frente a title
Los dos dan el nombre accesible. Las diferencias prácticas:
title |
aria-label |
|
|---|---|---|
| Produce tooltip nativo | Sí | No |
| Visible en el DOM como texto | Sí | No |
| Se traduce automáticamente | Sí, es contenido | No siempre, es un atributo |
| Funciona sin ARIA | Depende del motor | Necesita un rol adecuado |
| Admite formato | No, es texto plano | No |
El punto de la traducción es el que más pesa en un producto internacional. Los sistemas de traducción automática del navegador y las herramientas de localización tratan el contenido de un elemento como texto traducible; los atributos, no siempre. Un title se traduce; un aria-label puede quedarse en el idioma original.
Recomendación: title como opción por omisión, y aria-label cuando el tooltip nativo estorbe o cuando el SVG sea generado y no quieras añadir nodos.
Si usas los dos a la vez, aria-label gana: el orden de precedencia del cálculo del nombre accesible pone aria-labelledby primero, aria-label segundo, y el contenido (incluido title) después.
desc y cuándo se anuncia
desc produce la descripción accesible, que es un campo distinto del nombre. Los lectores de pantalla la tratan de forma diferente: el nombre se anuncia siempre, la descripción solo en ciertos modos o tras una pausa, y algunos usuarios la tienen desactivada.
Eso tiene una consecuencia de diseño importante: no pongas en desc información imprescindible. Si un dato es necesario para entender el gráfico, tiene que estar en un sitio que se anuncie siempre. La descripción es para el detalle adicional.
Para un gráfico de datos, ni el nombre ni la descripción bastan, y ese es el tema de una gráfica de datos accesible.
Un generador de identificadores
Como los identificadores tienen que ser únicos, conviene tener el generador resuelto:
let n = 0;const idUnico = (prefijo) => `${prefijo}-${(++n).toString(36)}`;function graficoAccesible(titulo, descripcion, contenido) { const t = idUnico('svg-t'); const d = idUnico('svg-d'); return `<svg role="img" aria-labelledby="${t} ${d}" viewBox="0 0 100 100"> <title id="${t}">${titulo}</title> <desc id="${d}">${descripcion}</desc> ${contenido} </svg>`;}En un entorno con renderizado de servidor e hidratación, el contador propio produce identificadores distintos en servidor y en cliente. Usa el generador estable que ofrezca tu framework.