Back

Usando prefers-reduced-motion para Animações Acessíveis

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: reduce e no-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.matchMedia e listeners como o hook useReducedMotion() 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-motion pode 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 movimento
  • no-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.

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:

  1. Abra o DevTools → aba Rendering (via menu de três pontos → More toolsRendering)
  2. Encontre “Emulate CSS media feature prefers-reduced-motion”
  3. 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..

OpenReplay