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 escribes | Lo que sale al DOM | Cuándo |
|---|---|---|
sin label | aria-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.
sizees 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;transformyopacitylas 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 losdefaultcíclicos: una nube a la deriva es lenta a propósito. - Rotación de más de 720° — un aviso. Cuatro:
orbitycalendar-cog/spina 1080°, máscloud-sync/holdycloud-backup/holda 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
holdretiene 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.