Uso de prefers-reduced-motion para animaciones accesibles
Algunos usuarios experimentan mareos, náuseas o desorientación con las interfaces animadas. La función de medios CSS prefers-reduced-motion te permite detectar su preferencia de sistema y responder con un movimiento más seguro, sin eliminar todo el atractivo visual de tu UI. Este artículo aborda la implementación práctica de CSS y JavaScript en patrones de componentes comunes.
Puntos clave
prefers-reduced-motiones una media query CSS que lee una configuración de accesibilidad a nivel del sistema operativo, con dos valores:reduceyno-preference.- El objetivo es reducir o reemplazar el movimiento no esencial, no eliminar la animación por completo: los desvanecimientos de opacidad y las duraciones más cortas siguen siendo alternativas más seguras.
- Utiliza el enfoque CSS progresivo (opt-in) para proteger a los usuarios que no han establecido explícitamente una preferencia pero que aún pueden ser sensibles al movimiento.
- Para animaciones controladas por JavaScript,
window.matchMediay listeners como el hookuseReducedMotion()de Motion mantienen el comportamiento sincronizado con los cambios del sistema. - El panel Rendering de Chrome DevTools emula la preferencia, por lo que puedes hacer pruebas sin modificar la configuración de tu sistema operativo.
prefers-reduced-motionpuede ayudar a cumplir con WCAG 2.3.3, pero no con el contenido que se reproduce automáticamente o en bucle, que aún necesita controles explícitos de pausa según WCAG 2.2.2.
Qué hace realmente prefers-reduced-motion
prefers-reduced-motion es una media query CSS madura y ampliamente soportada, no una API nueva. Lee una configuración de accesibilidad a nivel del sistema que el usuario ha habilitado en su SO (macOS, Windows, iOS, Android, Linux). Los dos valores son:
reduce: el usuario ha solicitado menos movimientono-preference: no se ha establecido ninguna preferencia
Vale la pena destacar: @media (prefers-reduced-motion) es una forma abreviada equivalente a @media (prefers-reduced-motion: reduce).
La entrada de MDN Web Docs cubre la sintaxis completa y la tabla de compatibilidad de navegadores, mientras que Can I use confirma el amplio soporte en los motores de navegador modernos.
El modelo mental correcto: reducir, no eliminar
Un error común es tratar esto como un interruptor para desactivar toda animación. Ese no es el objetivo. La guía, incluida WCAG 2.3.3 (“Animation from Interactions”, Nivel AAA), es reducir o reemplazar el movimiento no esencial, particularmente:
- Movimientos a gran escala (parallax, zoom, paneles deslizantes)
- Elementos que giran o rotan
- Animaciones activadas por scroll que mueven contenido a través del viewport
Los desvanecimientos de opacidad, las transiciones de color y las duraciones más cortas son generalmente alternativas más seguras. Un modal que aparece con un fade en lugar de surgir desde abajo sigue comunicando el cambio de estado sin provocar incomodidad vestibular.
Implementación en CSS: dos enfoques
Defensivo (opt-out): Define los estilos animados por defecto y luego anúlalos dentro de la media query.
.modal {
transform: translateY(20px);
opacity: 0;
transition: transform 300ms ease, opacity 300ms ease;
}
@media (prefers-reduced-motion: reduce) {
.modal {
transform: none;
transition: opacity 200ms ease;
}
}
Progresivo (opt-in): Define estilos estáticos por defecto y solo añade movimiento cuando el usuario no haya rechazado expresamente esta opción.
/* Static by default */
.modal {
opacity: 0;
transition: opacity 200ms ease;
}
@media (prefers-reduced-motion: no-preference) {
.modal {
transform: translateY(20px);
transition: transform 300ms ease, opacity 300ms ease;
}
}
El enfoque progresivo es más seguro para los usuarios que no han establecido una preferencia pero que aún podrían ser sensibles al movimiento.
Las propiedades personalizadas de CSS hacen que esto sea escalable en una base de código grande:
:root {
--duration: 300ms;
--easing: ease;
}
@media (prefers-reduced-motion: reduce) {
:root {
--duration: 0.01ms;
}
}
.drawer {
transition: transform var(--duration) var(--easing);
}
Detectar la preferencia en JavaScript
Para animaciones controladas por JS, utiliza window.matchMedia:
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)');
if (prefersReduced.matches) {
// skip or simplify animation
}
// React to live preference changes (e.g., user toggles OS setting)
prefersReduced.addEventListener('change', (e) => {
if (e.matches) {
// pause or replace animations
}
});
Esto es útil para carruseles, efectos activados por scroll o cualquier animación controlada fuera de CSS.
Discover how at OpenReplay.com.
Librerías de animación: Framer Motion y Motion.dev
Si utilizas Motion for React (la librería que muchos siguen llamando Framer Motion; su documentación ahora vive en Motion.dev), el hook useReducedMotion() gestiona esto de forma elegante:
import { motion } from "motion/react";
import { useReducedMotion } from "motion/react";
function Drawer({ isOpen }) {
const reduce = useReducedMotion();
return (
<motion.div
animate={{ x: isOpen ? 0 : -300, opacity: isOpen ? 1 : 0 }}
transition={reduce ? { duration: 0 } : { duration: 0.3 }}
/>
);
}
El hook lee la preferencia del sistema de forma reactiva, por lo que se mantiene sincronizado si el usuario cambia la configuración de su SO durante la sesión.
Pruebas con Chrome DevTools
No necesitas modificar la configuración de tu SO para hacer pruebas. En Chrome DevTools:
- Abre DevTools → pestaña Rendering (mediante el menú de tres puntos → More tools → Rendering)
- Busca “Emulate CSS media feature prefers-reduced-motion”
- Establécelo en
reduce
Tu página responderá de inmediato, lo que te permitirá verificar cada componente animado sin tocar las preferencias del sistema.
Una nota sobre la cobertura de WCAG
prefers-reduced-motion puede ayudar a cumplir con WCAG 2.3.3, pero no cubre todo. Los videos que se reproducen automáticamente, los GIFs en bucle y el contenido en movimiento continuo aún pueden requerir controles explícitos de pausa/detención según WCAG 2.2.2 (“Pause, Stop, Hide”). La media query maneja bien las animaciones activadas por interacción, pero el movimiento de fondo persistente necesita una solución aparte.
Conclusión práctica
Audita tus componentes animados (modales, drawers, efectos hover, carruseles, transiciones de página) y decide para cada uno si reducir la duración, sustituir el movimiento por opacidad o saltarte la animación por completo. Utiliza propiedades personalizadas de CSS para centralizar la lógica, matchMedia cuando JavaScript controle la animación, y Chrome DevTools para verificar sin cambiar tu SO.
Conclusión
Respetar prefers-reduced-motion es una de las mejoras de accesibilidad de menor esfuerzo y mayor impacto disponibles para los desarrolladores front-end. La tecnología es estable, el soporte de navegadores es excelente y los patrones de implementación son sencillos en CSS, JavaScript y las librerías de animación modernas. El verdadero trabajo consiste en cambiar tu modelo mental: la animación se convierte en una capa que se puede atenuar para usuarios sensibles, en lugar de una característica que se activa o desactiva. Incorpora ese hábito en cada componente que entregues.
Preguntas frecuentes
Establecer la duración en un valor muy pequeño como 0.01ms suele ser preferible a cero. Conserva el evento transitionend, de modo que la lógica de JavaScript que escucha la finalización de la animación continúa ejecutándose. Un cero real puede omitir el evento en algunos navegadores y romper máquinas de estado que dependan de él. El resultado visual es idéntico a un cambio instantáneo.
Puede hacerlo, pero el comportamiento difiere entre navegadores e implementaciones. Un enfoque más seguro es envolver el scroll suave de CSS dentro de @media (prefers-reduced-motion: no-preference), de modo que los usuarios con movimiento reducido siempre reciban un scroll instantáneo. Si implementas una animación de scroll personalizada en JavaScript, también debes verificar la preferencia manualmente y recurrir a un scroll instantáneo usando window.scrollTo con behavior establecido en auto.
No. Las animaciones más cortas u omitidas suelen mejorar la capacidad de respuesta percibida porque los usuarios ven el contenido asentarse más rápido. Interaction to Next Paint a menudo se beneficia, ya que las transiciones ya no retrasan la retroalimentación visual. La única salvedad es eliminar animaciones que comunicaban cambios de estado: reemplázalas con desvanecimientos de opacidad o cambios de color para que la interfaz siga sintiéndose receptiva en lugar de abrupta.
Sí, y es una buena práctica para los usuarios en dispositivos compartidos o aquellos que no han descubierto la opción del SO. Almacena la preferencia en localStorage y combínala con el resultado de la media query mediante un OR lógico. De esta manera, tanto la configuración del SO como el toggle dentro de la aplicación pueden activar el movimiento reducido, dando a los usuarios el máximo control sin anular su preferencia del sistema.
Truly understand users experience
See every user interaction, feel every frustration and track all hesitations with OpenReplay — the open-source digital experience platform. It can be self-hosted in minutes, giving you complete control over your customer data. . Check our GitHub repo and join the thousands of developers in our community..