Temas

Iconos accesibles

La mayoría de los iconos van junto a una etiqueta y deben pasar desapercibidos para las tecnologías de apoyo; unos pocos transmiten significado por sí solos y necesitan un nombre. glyphfox resuelve bien el primer caso por defecto y convierte el segundo en una sola prop.

Decorativos por defecto

Todos los iconos de glyphfox se renderizan con aria-hidden="true" a menos que les des un nombre. Los lectores de pantalla los omiten, que es justo lo que quieres para un icono junto a texto visible: el texto ya dice qué hace el control, y oír además "imagen" en el anuncio solo es ruido.

output.html
<!-- <DownloadIcon /> next to a label -->
<button type="button">
  <svg class="glyphfox glyphfox-download" aria-hidden="true" ...>...</svg>
  Download
</button>

El mismo comportamiento predeterminado se aplica en todas partes: los componentes de React, Vue, Svelte y Angular, el elemento glyph-fox, el runtime para HTML plano y las cadenas de toSvg. Solo tienes que cambiarlo cuando un icono es lo único que transmite su significado.

Iconos que transmiten significado

Pasa title y el icono se convierte en una imagen con nombre. El renderizador añade tres cosas: un elemento <title> como primer hijo, role="img" y aria-label con el mismo texto, para que el nombre se exponga de forma coherente en todos los navegadores y lectores de pantalla.

Status.jsx
import { CircleCheckIcon, TriangleAlertIcon } from 'glyphfox/react';

export function SyncStatus({ ok }) {
  return ok
    ? <CircleCheckIcon title="Synced" color="#15803d" />
    : <TriangleAlertIcon title="Sync failed" color="#b91c1c" />;
}
output.html
<svg class="glyphfox glyphfox-circle-check" role="img" aria-label="Synced" ...>
  <title>Synced</title>
  ...
</svg>

En React, Vue y Svelte también puedes pasar aria-label o aria-labelledby directamente al componente. Se aplican al svg y el icono deja de ser decorativo por sí solo, sin el elemento <title> ni su tooltip al pasar el cursor. decorative anula la elección automática en ambos sentidos: decorative oculta un icono aunque tenga título, y decorative={false} lo expone.

Escribe el nombre según lo que significa el icono en ese lugar, no según lo que representa. Un icono trash es "Eliminar borrador", no "Papelera"; un circle check en una lista de pagos es "Pagado".

Botones y enlaces con solo un icono

Cuando un icono es el único contenido de un botón o un enlace, ponle nombre al control, no al icono. El control es lo que recibe el foco y lo que anuncia el lector de pantalla; el icono de dentro sigue siendo decorativo.

Toolbar.jsx
import { XIcon, SettingsIcon, SearchIcon } from 'glyphfox/react';

export function Toolbar({ onClose }) {
  return (
    <>
      <button type="button" aria-label="Close dialog" onClick={onClose}>
        <XIcon />
      </button>

      <a href="/settings" aria-label="Settings">
        <SettingsIcon />
      </a>

      <button type="submit">
        <SearchIcon />
        <span className="visually-hidden">Search the docs</span>
      </button>
    </>
  );
}

El texto visualmente oculto, el tercer patrón, lo traducen las herramientas de traducción del navegador, que a menudo omiten los atributos; dale preferencia en sitios multilingües. Una clase visually-hidden típica recorta el elemento a un píxel sin quitarlo del árbol de accesibilidad.

app.css
.visually-hidden {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}

Los botones de alternancia necesitan indicar su estado, no un nombre nuevo. Un botón de menú que se transforma de menu a x mantiene su etiqueta y define aria-expanded; un botón de reproducción que se transforma en pause cambia su etiqueta o usa aria-pressed. El morph es la mitad visual de ese cambio; la guía de iconos animados muestra las dos mitades juntas.

Estado, color y contraste

El color por sí solo no debe transmitir significado (WCAG 1.4.1). Usa formas distintas para estados distintos, como circle-check, circle-alert y circle-x, y acompáñalas de texto siempre que puedas. Las insignias también ayudan: file+check y file+alert se diferencian por la forma, no solo por el color.

Los iconos que las personas necesitan para entender la página o encontrar un control deben alcanzar un contraste de 3 a 1 con su fondo (WCAG 1.4.11, contraste no textual). Mide el color del trazo, porque es lo que dibuja la forma; la capa translúcida del estilo duotone es una decoración por encima. Los iconos decorativos junto a texto no están cubiertos por esta regla, pero un contraste bajo hace igualmente que cueste más identificarlos de un vistazo.

Los trazos finos parecen más claros de lo que indica su color, porque el suavizado de bordes los mezcla con el fondo. Por debajo de 24 píxeles, glyphfox lo compensa haciendo el trazo hasta un 30 por ciento más grueso; a 14 o 16 píxeles y con un color apagado, un weight de 2.25 o 2.5 mantiene los iconos legibles. absoluteWeight desactiva la compensación, así que úsalo con cuidado en tamaños pequeños.

Movimiento

Todos los estados vivos y todos los morphs respetan prefers-reduced-motion: reduce: los presets no se reproducen, los morphs de CSS cambian sin transición y los morphs de los componentes saltan a su forma final. El estado final sigue visible, así que no se pierde información.

El contenido que se mueve por sí solo durante más de cinco segundos necesita una forma de pausarlo (WCAG 2.2.2). Vincula las animaciones en bucle, como spin, al proceso que representan y detenlas cuando termine, y evita los efectos en bucle para llamar la atención, como una campana que se agita sin parar.

Diseños de derecha a izquierda

En interfaces en árabe, hebreo o persa, los iconos que apuntan en el sentido de lectura deben invertirse: las flechas de atrás y adelante, los chevrons de las migas de pan, responder y deshacer. mirrorRtl añade una clase que refleja el icono siempre que esté dentro de dir="rtl", de modo que el mismo marcado funciona en ambas direcciones.

Pager.jsx
import { ChevronLeftIcon, ChevronRightIcon } from 'glyphfox/react';

export function Pager({ onPrev, onNext }) {
  return (
    <nav aria-label="Pagination">
      <button type="button" onClick={onPrev}><ChevronLeftIcon mirrorRtl /> Previous</button>
      <button type="button" onClick={onNext}>Next <ChevronRightIcon mirrorRtl /></button>
    </nav>
  );
}

No reflejes los iconos cuya dirección no sigue al texto: los controles multimedia como play y fast-forward siguen apuntando a la derecha, un clock sigue girando en el sentido de las agujas del reloj, un check se queda como está y rotate-cw sigue significando sentido horario.

Máscaras, sprites, el elemento y Angular

  • Las clases de Tailwind CSS renderizan un elemento vacío con una máscara, que no tiene ninguna semántica. Añade aria-hidden="true" y dale al control una etiqueta visualmente oculta. En el modo de alto contraste de Windows, un icono de máscara puede desaparecer; la guía de Tailwind CSS explica cómo solucionarlo.
  • Los sprites SVG dejan en tus manos el svg exterior: añade aria-hidden="true" para un icono decorativo, o role="img" y aria-label para uno con significado. La guía de sprites tiene ejemplos.
  • El elemento glyph-fox y el gf-icon de Angular renderizan el svg dentro del elemento host. Un aria-label en el host convierte el svg interno en una imagen, pero no le da nombre, y una etiqueta en un elemento genérico no se anuncia de forma fiable. Usa el atributo o input title, que sí da nombre al svg interno.
index.html
<!-- names the inner svg: role img, aria-label and a title -->
<glyph-fox name="circle-alert" title="Payment failed"></glyph-fox>

<!-- Angular -->
<gf-icon name="circle-alert" title="Payment failed"></gf-icon>

Pruébalo

  1. Abre el árbol de accesibilidad en las herramientas de desarrollo de tu navegador y selecciona el control: su nombre debe ser la etiqueta que escribiste, y los iconos decorativos no deben aparecer en absoluto.
  2. Navega con un lector de pantalla: VoiceOver en macOS e iOS, NVDA en Windows, TalkBack en Android. Presta atención a los nombres duplicados y a cualquier cosa que se anuncie solo como "imagen".
  3. Emula prefers-reduced-motion y forced-colors en las opciones de renderizado de las DevTools de Chromium y comprueba que los iconos siguen visibles y quietos.
  4. Comprueba con un selector de color el contraste de los iconos con significado frente al fondo real, incluidos los estados de hover y de foco.

Las props que importan aquí

PropTipoPor defectoDescripción
colorstringcurrentColorColor principal.
weightnumber2Trazo de 1 a 3 en la cuadrícula de 24, con compensación óptica por debajo de 24 px.
absoluteWeightbooleanfalseMantiene el trazo en weight píxeles a cualquier tamaño.
mirrorRtlbooleanfalseRefleja en layouts de derecha a izquierda.
titlestringningunoNombre accesible: title, role img y aria-label.
decorativebooleantrue sin titleOculta el icono a las tecnologías de apoyo.

Errores comunes

Dar nombre al control y al icono
Un title en un icono dentro de un botón que ya tiene etiqueta hace que los lectores de pantalla anuncien ambos, y el <title> también aparece como tooltip al pasar el cursor. Pon nombre a una sola cosa: el control.
Una imagen sin nombre
decorative={false} sin title ni aria-label expone una imagen sin nombre, que los lectores de pantalla anuncian como "imagen" o por su nombre de archivo. Dale un nombre a cada icono expuesto.
Estados que solo muestra el icono
Un morph de copy a check es invisible para un lector de pantalla. Anuncia el resultado con texto: cambia la etiqueta del botón o pon "Copiado" en una live region con role="status".
Tooltips generados por títulos
El tooltip del navegador que genera <title> solo aparece al pasar el cursor por encima, nunca con el foco ni al tocar. No pongas ahí información que no esté disponible de otra forma.
Etiquetas en el elemento host
En glyph-fox y gf-icon, usa title en lugar de aria-label, como se describe más arriba.

Preguntas frecuentes

¿Todos los iconos necesitan un título?

No. La mayoría de los iconos están junto a un texto o dentro de un control con etiqueta, y ahí un título solo añade ruido. Pon título a los iconos que aparecen solos y transmiten información, como un icono de estado en una celda de una tabla.

¿Basta con aria-hidden para los iconos decorativos?

Sí. aria-hidden="true", que glyphfox añade por defecto, elimina el icono del árbol de accesibilidad. Los iconos SVG no pueden recibir el foco, así que no hay nada más que ocultar.

Iconos de esta guía