Usando prefers-reduced-motion para Animações Acessíveis
Alguns usuários sentem tonturas, náuseas ou desorientação ao interagir com interfaces animadas. A media feature CSS prefers-reduced-motion permite detectar essa preferência do sistema e responder com um movimento mais seguro — sem remover todo o polimento visual da sua UI. Este artigo aborda implementações práticas em CSS e JavaScript em padrões comuns de componentes.
Principais Conclusões
prefers-reduced-motioné uma media query CSS que lê uma configuração de acessibilidade no nível do sistema operacional, com dois valores:reduceeno-preference.- O objetivo é reduzir ou substituir movimentos não essenciais, não eliminar completamente as animações — fades de opacidade e durações reduzidas continuam sendo alternativas mais seguras.
- Use a abordagem CSS progressiva (opt-in) para proteger usuários que não definiram uma preferência explícita, mas que ainda podem ser sensíveis a movimento.
- Para animações controladas por JavaScript,
window.matchMediae listeners como o hookuseReducedMotion()do Motion mantêm o comportamento sincronizado com as alterações do sistema. - O painel Rendering do Chrome DevTools emula a preferência, permitindo testar sem alterar as configurações do seu sistema operacional.
prefers-reduced-motionpode ajudar a atender ao WCAG 2.3.3, mas não cobre conteúdo com autoplay ou em loop, que ainda precisa de controles explícitos de pausa conforme o WCAG 2.2.2.
O que prefers-reduced-motion Realmente Faz
prefers-reduced-motion é uma media query CSS madura e amplamente suportada — não é uma API nova. Ela lê uma configuração de acessibilidade no nível do sistema que o usuário ativou no seu SO (macOS, Windows, iOS, Android, Linux). Os dois valores são:
reduce— o usuário solicitou menos movimentono-preference— nenhuma preferência foi definida
Vale notar: @media (prefers-reduced-motion) é uma forma abreviada equivalente a @media (prefers-reduced-motion: reduce).
A entrada no MDN Web Docs cobre a sintaxe completa e a tabela de compatibilidade entre navegadores, enquanto o Can I use confirma o amplo suporte entre os motores de navegadores modernos.
O Modelo Mental Correto: Reduzir, Não Remover
Um erro comum é tratar isso como um interruptor para desligar todas as animações. Esse não é o objetivo. A orientação — incluindo o WCAG 2.3.3 (“Animation from Interactions”, Nível AAA) — é reduzir ou substituir movimentos não essenciais, particularmente:
- Movimentos em larga escala (parallax, zoom, painéis deslizantes)
- Elementos girando ou rotacionando
- Animações acionadas pela rolagem que movem conteúdo pela viewport
Fades de opacidade, transições de cor e durações reduzidas são geralmente alternativas mais seguras. Um modal que aparece com fade-in em vez de surgir voando de baixo ainda comunica a mudança de estado sem provocar desconforto vestibular.
Implementação em CSS: Duas Abordagens
Defensiva (opt-out): Define os estilos animados por padrão e, em seguida, sobrescreve dentro da 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;
}
}
Progressiva (opt-in): Define estilos estáticos por padrão e só adiciona movimento quando o usuário não optou por sair.
/* Estático por padrão */
.modal {
opacity: 0;
transition: opacity 200ms ease;
}
@media (prefers-reduced-motion: no-preference) {
.modal {
transform: translateY(20px);
transition: transform 300ms ease, opacity 300ms ease;
}
}
A abordagem progressiva é mais segura para usuários que não definiram uma preferência, mas que ainda podem ser sensíveis a movimento.
Custom properties CSS tornam isso escalável em uma 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);
}
Detectando a Preferência em JavaScript
Para animações controladas por JS, use window.matchMedia:
const prefersReduced = window.matchMedia('(prefers-reduced-motion: reduce)');
if (prefersReduced.matches) {
// pular ou simplificar a animação
}
// Reagir a mudanças de preferência em tempo real (ex.: usuário altera a config do SO)
prefersReduced.addEventListener('change', (e) => {
if (e.matches) {
// pausar ou substituir animações
}
});
Isso é útil para carrosséis, efeitos acionados pela rolagem ou qualquer animação controlada fora do CSS.
Discover how at OpenReplay.com.
Bibliotecas de Animação: Framer Motion e Motion.dev
Se você usa Motion for React (a biblioteca que muitos ainda chamam de Framer Motion — sua documentação agora reside em Motion.dev), o hook useReducedMotion() lida com isso 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 }}
/>
);
}
O hook lê a preferência do sistema de forma reativa, mantendo-se sincronizado caso o usuário altere a configuração do SO no meio da sessão.
Testando com Chrome DevTools
Você não precisa alterar as configurações do seu SO para testar. No Chrome DevTools:
- Abra o DevTools → aba Rendering (via menu de três pontos → More tools → Rendering)
- Encontre “Emulate CSS media feature prefers-reduced-motion”
- Defina como
reduce
Sua página responderá imediatamente, permitindo verificar todos os componentes animados sem mexer nas preferências do sistema.
Uma Nota sobre a Cobertura do WCAG
prefers-reduced-motion pode ajudar a atender ao WCAG 2.3.3, mas não cobre tudo. Vídeos com autoplay, GIFs em loop e conteúdo em movimento contínuo ainda podem exigir controles explícitos de pausa/parada conforme o WCAG 2.2.2 (“Pause, Stop, Hide”). A media query lida bem com animações acionadas por interação — movimentos de fundo persistentes precisam de uma solução separada.
Conclusão Prática
Faça uma auditoria nos seus componentes animados — modais, drawers, efeitos de hover, carrosséis, transições de página — e decida para cada um se vai reduzir a duração, trocar o movimento por opacidade ou pular a animação completamente. Use custom properties CSS para centralizar a lógica, matchMedia quando o JavaScript controlar a animação e o Chrome DevTools para verificar sem alterar o seu SO.
Conclusão
Respeitar prefers-reduced-motion é uma das vitórias de acessibilidade de menor esforço e maior impacto disponíveis para desenvolvedores front-end. A tecnologia é estável, o suporte dos navegadores é excelente e os padrões de implementação são simples em CSS, JavaScript e bibliotecas modernas de animação. O verdadeiro trabalho é mudar o modelo mental: a animação passa a ser uma camada que pode ser atenuada para usuários sensíveis, em vez de uma funcionalidade a ser ligada ou desligada. Incorpore esse hábito em cada componente que você publicar.
FAQs
Definir a duração com um valor muito pequeno, como 0.01ms, costuma ser preferível a zero. Isso preserva o evento transitionend, de forma que a lógica JavaScript que escuta a conclusão da animação continue sendo disparada. Um zero verdadeiro pode pular o evento em alguns navegadores e quebrar máquinas de estado que dependem disso. O resultado visual é idêntico a uma mudança instantânea.
Pode afetar, mas o comportamento varia entre navegadores e implementações. Uma abordagem mais segura é envolver o smooth scrolling CSS dentro de @media (prefers-reduced-motion: no-preference), para que usuários com reduced-motion sempre recebam rolagem instantânea. Se você implementar animações de scroll personalizadas em JavaScript, também deve verificar a preferência manualmente e cair em uma rolagem instantânea usando window.scrollTo com behavior definido como auto.
Não. Animações mais curtas ou puladas geralmente melhoram a responsividade percebida porque os usuários veem o conteúdo se estabilizar mais rápido. O Interaction to Next Paint frequentemente se beneficia, já que as transições não atrasam mais o feedback visual. A única ressalva é remover animações que comunicavam mudanças de estado — substitua-as por fades de opacidade ou mudanças de cor para que a interface ainda pareça responsiva, em vez de abrupta.
Sim, e é uma boa prática para usuários em dispositivos compartilhados ou para aqueles que não descobriram a opção do SO. Armazene a preferência no localStorage e combine com o resultado da media query usando OR lógico. Dessa forma, tanto a configuração do SO quanto o toggle dentro do app podem acionar o reduced motion, dando aos usuários o máximo controle sem sobrescrever a preferência do 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..