Accesibilidad

Dos decisiones, las dos tomadas para que no tengas que combinar banderas: un icono es decorativo o semántico, y el movimiento reducido significa cosas distintas en un icono que en un morph.

Una regla, no una matriz

No hay que coordinar aria-hidden con aria-label. Solo pones label, o no lo pones:

Lo que escribesLo que sale al DOMCuándo
sin labelaria-hidden="true"El icono acompaña a un texto que ya dice lo mismo.
label="Save"aria-hidden="false" + aria-label="Save"El icono es el contenido: un botón sin texto.
<!-- Decorative: the button already says "Save" -->
<button><gf-icon [iconDef]="save" /> Save</button>

<!-- Semantic: no text, the icon carries the meaning -->
<button><gf-icon [iconDef]="save" label="Save" /></button>

decorative viene en true por default y casi nunca hay que tocarlo: es el label el que decide. Aplica igual a <gf-icon> y a <gf-icon-morph>.

prefers-reduced-motion

Ambos componentes lo respetan por default (respectReducedMotion = true). Pero respetar no significa lo mismo en los dos, y esa diferencia es deliberada, no un descuido de herencia:

En <gf-icon>: se queda quieto

La coreografía es un adorno sobre un icono que ya es el correcto. Quitar el movimiento no quita información: la campana sigue siendo una campana.

En <gf-icon-morph>: salta al icono nuevo

Aquí quedarse quieto dejaría al usuario mirando el icono equivocado. El destino no es adorno, es estado: si el botón pasó de "reproducir" a "pausa", tiene que verse "pausa".

Se respeta el movimiento reducido quitando el movimiento, no el cambio de estado. El morph se salta la transición y pinta el destino de una.

Si necesitas ignorar la preferencia del sistema — un caso raro, y la carga de justificarlo es tuya — el override existe: [respectReducedMotion]="false".

Lo que el componente no decide por ti

  • El color. El SVG usa stroke="currentColor", así que hereda el color del texto. El contraste contra el fondo lo controlas tú.
  • El área de toque. size es el tamaño del dibujo. Un botón de icono necesita su propio padding para llegar a los 24×24 CSS px de objetivo mínimo.
  • El foco. El icono no es interactivo: no recibe foco ni teclado. Si va dentro de algo clickeable, ese algo debe ser un <button> o un <a> real.

Cómo se mantiene barato el movimiento del catálogo

Cada coreografía curada la analiza un linter determinista — el mismo Motion Inspector que puedes abrir sobre cualquier icono del catálogo, corriendo como check de CI sobre todo el curado en cada PR que lo toca. Una implementación, dos lugares.

Reporta cuatro cosas, medidas sobre 1767 iconos y 4795 variantes:

  • Animar d — esto rompe el build. Cambiar los datos del trazo lo redibuja en cada cuadro; transform y opacity las mueve el compositor sin repintar. Hoy el catálogo tiene cero, así que la regla protege contra una regresión en vez de señalar deuda existente. El trazo progresivo (autoDraw) queda fuera: no tiene equivalente barato.
  • Duración de más de 1200 ms — un aviso. 54 variantes lo pasan, casi todas iconos que cuentan una historia corta (un dado rodando, el clima). Los bucles quedan exentos por nombre (idle, wander) y también los default cíclicos: una nube a la deriva es lenta a propósito.
  • Rotación de más de 720° — un aviso. Cuatro: orbit y calendar-cog/spin a 1080°, más cloud-sync/hold y cloud-backup/hold a 7200°. Esas dos no son un frenesí: son un giro continuo escrito como una sola rotación finita muy larga (20 vueltas en 24 s, unos 300°/s) que se revierte al salir el puntero.
  • Una pose retenida por debajo de 0.8 unidades — un aviso. Un hold retiene su último keyframe, así que esa pose es la variante; por debajo de ese umbral no se distingue del reposo. Quedan ocho, todos iconos cuya geometría no deja margen para crecer dentro del lienzo.

Solo la primera rompe el build. Las otras dos miden costo, no correctitud, y una coreografía cara puede ser la decisión correcta para un icono concreto — un check que falla por gusto se desactiva en una semana.

Esta documentación describe la v3.3.1, la versión publicada en npm.

Última actualización

Editar esta página en GitHub