Guía completa de accesibilidad para animación y movimiento: diseñar e implementar movimiento como “información cómoda”
Resumen (puntos clave primero)
- Verbaliza el propósito y limita el movimiento a animaciones significativas que apoyen la comprensión, la atención y el feedback, no el adorno decorativo.
- Diseña tiempo, distancia y dirección con cuidado, considerando mareo por movimiento, regulación de la atención y fotosensibilidad.
- Respeta los ajustes del SO (p. ej.,
prefers-reduced-motion
) y ofrece opciones de detener / reducir / alternativas.- Diseña cambios de estado, movimiento del foco y notificaciones con una expresión en tres capas: “visible + legible + tangible”.
- Esta guía empaqueta fragmentos de implementación (CSS/JS), procedimientos de revisión/prueba y PDCA para operaciones en un solo lugar.
Lectores objetivo (concretos): diseñadores UI/UX, ingenieros frontend, PMs de producto, QA, marca/marketing, creadores de e-learning y educación
Nivel de accesibilidad: aspira a WCAG 2.1 AA (usa AAA cuando sea factible)
1. Introducción: el movimiento no es “decoración”, es la puntuación de tu interfaz
La animación hace más que dar carácter a una UI; ayuda a visualizar relaciones, expresar el tiempo y vincular causa con efecto. El exceso de movimiento y las transiciones llamativas pueden causar fatiga, distracción y malestar similar al mareo.
Esta guía trata el movimiento como parte de la capa de información, explicando un diseño e implementación que sean “cómodos y clarificadores” para todos. Palabras guía: mantenlo mínimo, con propósito y siempre detenable.
2. Principios del diseño de movimiento: alinea propósito, timing y espacio
2.1 Empieza verbalizando el propósito
- Indicar flujo: muestra de dónde viene el contenido nuevo y a dónde va (p. ej., un panel lateral que entra).
- Revelar jerarquía: expresa padre/hijo o frente/fondo mediante escala y profundidad (p. ej., un modal con superposición).
- Feedback de estado: indica guardar/completar/error con un cambio breve (color, forma, icono, háptica).
- Guiar la atención: atrae suavemente la mirada a la info necesaria (un realce ligero, una sola vez).
→ Si un movimiento no encaja en lo anterior, considera eliminarlo.
2.2 Timing: corto y predecible
- Base sugerida: 200–300 ms; ajusta ±100 ms según peso/distancia de UI.
- Escalona, no amontones: para múltiples elementos, un escalonado de 20–40 ms mejora la percepción.
- Easing: para UI, ease-in-out; para énfasis, una curva tendente a desacelerar como
cubic-bezier(.2,.8,.2,1)
se siente suave. - No hagas esperar: evita animaciones largas e indeterminadas. Usa spinners + texto o skeletons para hacer visible el tiempo.
2.3 Espacio: distancia, dirección, escala
- Mantén distancias cortas: recorridos largos inducen náusea. Prefiere fundidos suaves + deslizamientos cortos.
- Consistencia direccional: menús que entran por la derecha deben salir por la derecha; tarjetas apiladas hacia arriba deben descartarse hacia arriba—mantén pares entrada/salida.
- Poca escala: una microescala de 1.0 → 1.03 comunica “presionado”. Define
transform-origin
intencionalmente.
3. Respeta fisiología y diferencias: mareo, fotosensibilidad, control de la atención
- Parallax y grandes paneos/zooms son grandes desencadenantes de mareo. Predetermina estático o mínimo.
- Destellos a ≥3 Hz pueden suponer riesgo de convulsión. Mejor ninguno; si no, mantén <3 veces/seg y limita el contraste de luminancia.
- Secuestros continuos de atención (sacudidas/pulsos constantes) sobrecargan a usuarios incl. con TDAH. Ofrece alternativas estáticas o control del usuario.
- Para audio/háptica, ofrece siempre silencio, detener y controles de intensidad.
4. Respeta los ajustes del SO: prefers-reduced-motion
es una “promesa”
Si el usuario elige “reducir movimiento” a nivel del SO, tu UI debe seguirlo.
/* Default (standard) */
.modal[open] {
animation: modal-in 240ms cubic-bezier(.2,.8,.2,1);
}
@keyframes modal-in {
from { opacity: 0; transform: translateY(12px) scale(.98); }
to { opacity: 1; transform: translateY(0) scale(1); }
}
/* Respect reduced motion */
@media (prefers-reduced-motion: reduce) {
.modal[open] { animation: none; transition: none; }
.modal { transform: none !important; opacity: 1 !important; }
}
- Tres niveles de reducción
- Detener (prioridad máxima).
- Suavizar (menor distancia/duración; sin bucles).
- Alternar (cambiar a señales sin movimiento como color, grosor, subrayado).
- Ajustes del usuario > marca. Prioriza salud y enfoque sobre los adornos de marca.
5. “Detenable, ocultable, revisable”: UI con soberanía del usuario
- Proporciona controles de Detener animación (a nivel de página o módulo).
- Para regiones siempre en movimiento (tickers, carruseles, video autoplay), ofrece siempre detener/pausa/ocultar.
- Para avisos importantes, no parpadees: usa texto + icono + color de forma redundante. Si se pierde, ofrece historial de toasts o centro de notificaciones.
Ejemplo: detener un ticker
<div class="ticker" role="region" aria-label="Latest updates">
<button type="button" class="ticker__toggle" aria-pressed="false">Pause</button>
<div class="ticker__track" aria-live="polite">…messages…</div>
</div>
<script>
const btn = document.querySelector('.ticker__toggle');
const track = document.querySelector('.ticker__track');
let running = true, timer = setInterval(roll, 4000);
btn.addEventListener('click', ()=>{
running = !running;
btn.setAttribute('aria-pressed', String(!running));
btn.textContent = running ? 'Pause' : 'Resume';
clearInterval(timer);
if (running) timer = setInterval(roll, 4000);
});
function roll(){ if(!running) return; /* Swap to the next message */ }
</script>
6. Buenas prácticas por escena común
6.1 Contenido dentro/fuera (fundido + deslizamiento corto)
- Lidera con fundido (evita parpadeos discontinuos; transiciona 0 → 1 suavemente).
- Apoya con deslizamiento corto (8–16 px) para indicar origen.
- Simetría: empareja entrar/salir (entrar = abajo→arriba, salir = arriba→abajo).
.card-enter { opacity: 0; transform: translateY(12px); }
.card-enter-active {
opacity: 1; transform: translateY(0);
transition: opacity 200ms, transform 200ms;
}
6.2 Enfoque y affordance de interacción
- La visibilidad de enfoque (
:focus-visible
) debe ser clara mediante color + grosor + offset. - En clic/toque, usa sombra breve y microescala para comunicar pulsación.
:focus-visible { outline: 3px solid #FF9900; outline-offset: 3px; }
.button:active { transform: translateY(1px) scale(.99); }
6.3 Feedback (éxito / precaución / error)
- Éxito: cambio de color + icono con una aparición suave y única.
- Precaución: amarillos + icono; sin parpadeo.
- Error: rojo + icono + háptica (si procede) y texto de remediación.
<div id="status" role="status" aria-atomic="true" class="sr-only"></div>
<script>
function saved(){ status.textContent = 'Save completed.'; }
function error(){ status.textContent = 'Save failed. Please check your network.'; }
</script>
6.4 Reordenar listas y drag-and-drop
- Ofrece alternativas al arrastre (botones subir/bajar) para que usuarios de un dedo y teclado completen la tarea.
- Durante el reordenamiento, usa movimientos cortos + sombra para comunicar solo el cambio de posición. Evita movimientos grandes o rotaciones.
7. No dependas solo del movimiento: diseña señales redundantes
- Usa el trío color + forma + texto para transmitir significado (p. ej., error = rojo + marca de error + mensaje).
- Proporciona texto para el contenido transmitido durante la animación (“Tienes 3 elementos nuevos”).
- Evita depender solo de sonido / háptica / movimiento; combina canales visuales, auditivos y táctiles.
8. Arquitectura de implementación: prefiere CSS, minimiza JS
- Prefiere transiciones/animaciones CSS: aprovecha rutas del compositor; céntrate en
transform
/opacity
. - Deja JS para estado: mostrar/ocultar, ARIA, alternar clases—solo lógica.
- Cancelabilidad: admite detener en cualquier momento vía
animation-play-state
o flags.
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)').matches;
if (prefersReduced) document.documentElement.classList.add('motion-reduced');
.motion-reduced * { animation: none !important; transition: none !important; }
9. Modales/overlays: sé suave con la profundidad
- Fondo: una superposición tranquila del 20–30%.
- Escala de entrada mínima (p. ej., 1 → 1.02); mantén movimiento en Z sutil.
- Incluye siempre focus trap + Esc para cerrar + devolver el foco al disparador. Incluso con movimiento mínimo, la completitud informativa es lo que cuenta.
10. Movimiento en gráficos, tutoriales y contenido educativo
- Gráficos: mantén trazos animados cortos; escena por serie (p. ej., barras por grupo) para ayudar a la percepción. En pausa, muestra etiquetas estáticas.
- Tutoriales: mueve solo el paso en foco; deja todo lo demás quieto. Limita el área en movimiento a ≤ 1/6 del viewport para reducir fatiga.
- E-learning: combina con subtítulos, resúmenes y diagramas para que los usuarios comprendan sin ver el movimiento.
11. Criterios de revisión para proteger la calidad (diseño + implementación)
- Propósito: ¿Qué comunica este movimiento? ¿Puede eliminarse? ¿Puede reemplazarse por texto/señales estáticas?
- Salud: ¿Hay destellos/movimientos grandes/rotación? ¿Se detiene con
prefers-reduced-motion
? - Predictibilidad: ¿Mismo componente = misma duración/easing? ¿Entrar/salir emparejados direccionalmente?
- Redundancia: ¿Además del movimiento, color/forma/texto transmiten significado?
- Operabilidad: ¿Teclado y lector de pantalla pueden percibir el estado? ¿Notificaciones vía
role="status"
? - Rendimiento: ¿Te ciñes a
transform
/opacity
sin sacudidas de layout? ¿Cerca de 60 fps? - Control e historial: ¿Siempre hay detener/reanudar y revisión?
12. Prueba de humo de 5 minutos
- Cambia el SO a Reducir movimiento y recorre pantallas clave: ¿se detienen las animaciones? ¿Se pierde información?
- Usa solo teclado para flujos de modal/toast/pestañas: ¿anuncios y movimiento de foco coinciden?
- Revisa historial de notificaciones y alternativas sin movimiento (color/negrita/subrayado).
- En móvil, asegúrate de no usar deslizamientos grandes frecuentes.
- DevTools performance: confirma mínimo Layout/Paint durante animaciones.
13. Ejemplo: toast accesible (con pausa e historial)
<div class="toaster" aria-live="polite" aria-atomic="true">
<div class="toast" hidden role="status">
<p class="toast__msg">Saved</p>
<button class="toast__pause" aria-pressed="false">Pause</button>
<button class="toast__close" aria-label="Close">×</button>
</div>
<details class="log">
<summary>Notification history</summary>
<ul class="log__list" aria-label="Notification history list"></ul>
</details>
</div>
.toast[hidden]{ display:none; }
.toast{
background:#111; color:#fff; border-radius:.75rem; padding:.75rem 1rem;
box-shadow:0 6px 24px rgba(0,0,0,.2); max-width:28rem;
transform: translateY(8px); opacity:0;
transition: opacity 180ms, transform 180ms;
}
.toast.show{ transform: translateY(0); opacity:1; }
@media (prefers-reduced-motion: reduce){
.toast{ transition:none; transform:none; opacity:1; }
}
const box = document.querySelector('.toast');
const msg = document.querySelector('.toast__msg');
const log = document.querySelector('.log__list');
const pauseBtn = document.querySelector('.toast__pause');
const closeBtn = document.querySelector('.toast__close');
let timer, paused = false;
function notify(text){
msg.textContent = text;
box.hidden = false; box.classList.add('show');
log.insertAdjacentHTML('afterbegin', `<li>${new Date().toLocaleTimeString()}: ${text}</li>`);
clearTimeout(timer);
timer = setTimeout(()=>{ if(!paused) hide(); }, 3000);
}
function hide(){ box.classList.remove('show'); setTimeout(()=> box.hidden = true, 200); }
pauseBtn.addEventListener('click',()=>{
paused = !paused;
pauseBtn.setAttribute('aria-pressed', String(paused));
pauseBtn.textContent = paused ? 'Resume' : 'Pause';
});
closeBtn.addEventListener('click', hide);
// Example: call on save completion
// notify('Save completed.');
14. Caso de estudio: rescatar una expresión de marca “sobre-animada”
Antes
- Fuerte parallax en el hero; fondo en movimiento constante.
- Transiciones de sección con zooms grandes; carrusel en autoplay sin detener.
- Alto bounce; usuarios con lector de pantalla informan “no puedo seguir la interfaz”.
Después
- Sustituir parallax por imágenes estáticas + fundido suave.
- Transiciones de sección con deslizamientos cortos + fundidos, y parada total bajo reducción de movimiento.
- Carrusel con autoplay desactivado; añadir controles pausa/reproducir y ant./sig.
- Resultados: tiempo en página +16%, finalización de scroll +22%, tickets de soporte (mareo/operabilidad) −78%.
15. Checklist (copiar/pegar para operaciones)
- [ ] Propósito del movimiento definido; movimiento innecesario eliminado
- [ ] Sin destellos/movimientos grandes/rotación, o son detenedores
- [ ]
prefers-reduced-motion
implementado para detener/suavizar/alternar - [ ] Duración/easing/dirección consistentes por componente
- [ ] Significado transmitido vía color/forma/texto, no solo movimiento
- [ ] Notificaciones anunciadas (p. ej.,
role="status"
) y visibles en historial - [ ] Teclado y lector de pantalla utilizables/entendibles
- [ ] Primero
transform
/opacity
; sin “thrash” de layout - [ ] Controles de detener / pausar / reanudar disponibles
- [ ] Pasa la prueba de humo de 5 minutos (SO reduce movimiento, teclado, historial, móvil)
16. Beneficios concretos por rol
- Diseñadores UI/UX: el movimiento guiado por propósito une expresión y función; revisiones más fluidas.
- Ingeniería frontend: CSS primero y JS mínimo producen implementaciones resistentes a regresiones y menos quejas gracias a reducción de movimiento.
- PMs/directores: equilibrio entre marca y salud, mejorando rebote y CVR.
- QA: verificación estable mediante checklist + prueba de humo.
- Educación/e-learning: protege el foco del alumno, reduciendo carga cognitiva.
- Usuarios: menos náusea, fatiga y omisiones—una experiencia más calmada y fiable.
17. Cierre: lo silencioso es amabilidad
- Elimina movimiento sin propósito. Para el necesario, mantenlo corto, cercano y predecible.
- Respeta reducción de movimiento del SO y ofrece siempre detener / suavizar / alternativas.
- Diseña señales redundantes (color/forma/texto) para que el significado no dependa del movimiento.
- Prefiere CSS y
transform
/opacity
para suavidad y eficiencia. - Ofrece detener, historial y anuncios para lector de pantalla para evitar pérdidas.
- Incorpora criterios de revisión y una prueba de 5 minutos en operaciones para elevar la calidad continuamente.
El movimiento debe ser una señal suave que guía. Que tu UI se siente a un lado del usuario y ofrezca un empujón ligero y oportuno solo cuando haga falta. Estoy contigo.