Pular para o conteúdo
Zumkai

prefers-reduced-motion: motion acessível sem matar o design

A palavra da especificação é reduzir, não remover. Por que animation: none !important erra dos dois lados, e a tabela de substituição que preserva o design.

  • acessibilidade
  • motion design
Comparação entre remover a animação e substituí-la por uma transição de opacidade.
Sumário
  1. O que de fato faz mal
  2. A sintaxe, e a escolha que ela força
  3. Por que animation: none !important é a resposta errada
  4. Substituir, não remover
  5. O lado JavaScript
  6. Quatro pontos que quase todo site esquece
  7. O que o WCAG exige, e o que não exige
  8. Como testar
  9. Perguntas frequentes
  10. Fontes

A palavra da especificação é reduzir, não remover.

A MDN lista três saídas: a interface pode remover, reduzir ou substituir a animação. A terceira é a que quase ninguém implementa.

A solução que circula é jogar animation: none !important em tudo. Ela erra dos dois lados.

Apaga o que comunica estado, como o spinner que diz que o sistema está trabalhando. E não encosta no parallax em JavaScript, que é o que de fato causa o problema.

O que de fato faz mal

Antes de decidir o que desligar, vale saber o que se está evitando, porque a resposta muda o alvo.

A finalidade documentada da preferência é atender pessoas com distúrbios vestibulares, do ouvido interno. O W3C descreve as reações como severas: tontura, náusea, enxaqueca, perda de foco, e casos em que a pessoa precisa ficar de cama para se recuperar. A MDN é específica sobre o gatilho: escalar ou deslocar objetos grandes provoca desconforto.

Daí sai um critério prático que não aparece na maior parte dos tutoriais:

PadrãoRiscoPor quê
Parallax e movimento de fundoAltoÁrea grande se deslocando fora de sincronia com o gesto
Zoom e escala de elemento grandeAltoSimula aproximação, engana o sistema vestibular
Rotação e giro contínuoAltoMovimento sem ponto de repouso
Deslizar seção inteiraMédioDepende da área e da distância
Deslocamento pequeno, poucos pixelsBaixoÁrea pequena, duração curta
Fade de opacidadeBaixoNão há movimento, só variação de luminosidade
Mudança de corBaixoSem componente espacial

A última linha é a chave do post inteiro. Se opacidade e cor são seguras, você não precisa entregar uma página estática para quem ligou a preferência. Precisa entregar a mesma página com outro vocabulário de transição.

A sintaxe, e a escolha que ela força

Dois valores, e a diferença entre eles não é simétrica.

css
/* a pessoa ligou a preferência no sistema */
@media (prefers-reduced-motion: reduce) { }

/* ninguém declarou preferência; avalia como falso quando reduce está ligado */
@media (prefers-reduced-motion: no-preference) { }

/* forma curta, equivalente a reduce */
@media (prefers-reduced-motion) { }

Isso força uma escolha de arquitetura que dá para inverter:

Padrão opt-out. Você anima por padrão e desliga dentro de reduce. É o mais comum e o mais frágil: num navegador que não suporta a consulta, o bloco de desligamento nunca roda e a animação toca.

Padrão opt-in. Você escreve a animação inteira dentro de no-preference. O estado sem animação vira o padrão, e o movimento é o acréscimo.

css
.card {
  opacity: 0;
  transform: translateY(24px);
}

/* sem suporte à consulta, este bloco não se aplica e nada anima */
@media (prefers-reduced-motion: no-preference) {
  .card {
    transition: opacity .5s ease, transform .5s ease;
  }
  .card.visivel { opacity: 1; transform: none; }
}

Um cuidado no exemplo acima. O estado inicial esconde o elemento, então a revelação depende da animação rodar. Sem ela, o conteúdo some.

O jeito seguro inverte isso: o estado final é o padrão, e a animação só decora a chegada. Ela nunca é o que torna o conteúdo visível.

Por que animation: none !important é a resposta errada

A receita curta que circula:

css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation: none !important;
    transition: none !important;
  }
}

Três problemas concretos, em ordem de gravidade.

Ela apaga animação que carrega informação. Spinner de carregamento, barra de progresso, indicador de gravação: todos comunicam estado por movimento. Desligados, a pessoa fica sem saber se o sistema travou. Tanto o critério 2.3.3 quanto o 2.2.2 do WCAG abrem exceção explícita para animação essencial à funcionalidade ou à informação transmitida, e essa exceção existe por esse motivo.

Ela não alcança o que mais machuca. Parallax feito com transform em JavaScript, animação de scroll com ScrollTrigger, vídeo de fundo em autoplay: nada disso é animation nem transition de CSS. O seletor universal passa longe do problema real.

Ela entrega uma experiência pior sem precisar. A pessoa pediu menos movimento, não menos design. Zerar toda transição faz a interface responder com trancos, o que piora a percepção de qualidade sem ganho de acessibilidade.

Existe uma variante menos ruim. Ela troca none por uma duração mínima em vez de cortar. Assim os eventos de fim de animação continuam disparando, e o JavaScript que depende deles não quebra:

css
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: .01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .01ms !important;
  }
}

Ainda é remédio de emergência para site legado. Para código novo, substitua em vez de cortar.

Substituir, não remover

A tabela que eu uso como referência de projeto:

Efeito originalSubstituição sob reduce
Entrada deslizando de baixoFade de opacidade, no lugar
Parallax de fundoFundo estático, sem deslocamento
Zoom em imagem no hoverMudança de brilho ou de borda
Carrossel com auto-avançoSem auto-avanço, só controle manual
Contador animadoValor final, direto
Transição de página deslizanteCorte seco ou fade curto
Vídeo de fundo em autoplayPrimeiro quadro como imagem estática
Rolagem suaveSalto direto ao destino

O princípio: preserve o significado, troque o veículo. Se o slide para cima dizia "isto é novo", o fade diz a mesma coisa sem deslocar área grande.

O lado JavaScript

CSS não alcança tudo. Para animação em código, a mesma consulta funciona via matchMedia, e o evento de mudança importa mais do que parece:

js
const consulta = window.matchMedia("(prefers-reduced-motion: reduce)");

function aplicar() {
  if (consulta.matches) {
    pararParallax();
    document.querySelectorAll("video[autoplay]").forEach(v => v.pause());
  } else {
    ligarParallax();
  }
}

aplicar();
consulta.addEventListener("change", aplicar);

Ler consulta.matches uma vez, no carregamento, é o erro mais comum. A pessoa pode ligar a preferência no meio da sessão, e sem o ouvinte de change a página continua se mexendo até um recarregamento.

Nas duas bibliotecas que dominam o mercado, o idioma já existe.

GSAP trata a preferência como mais uma condição no gsap.matchMedia(). Ele reverte sozinho o que foi criado ali dentro, animação e ScrollTrigger:

js
const mm = gsap.matchMedia();

mm.add({
  reduzir: "(prefers-reduced-motion: reduce)",
  normal: "(prefers-reduced-motion: no-preference)",
}, (ctx) => {
  const { reduzir } = ctx.conditions;
  gsap.from(".secao", {
    opacity: 0,
    y: reduzir ? 0 : 60,
    duration: reduzir ? 0.2 : 0.8,
    scrollTrigger: { trigger: ".secao", start: "top 85%" },
  });
});

Motion, a antiga Framer Motion, resolve no nível da aplicação com MotionConfig. Com reducedMotion="user", ela desativa animações de transform e de layout e mantém opacidade e cor de fundo, que é exatamente a substituição recomendada:

jsx
<MotionConfig reducedMotion="user">
  <App />
</MotionConfig>

Os valores aceitos são "user", "always" e "never". Para controle fino, o hook useReducedMotion() devolve um booleano e deixa você escolher a substituição caso a caso, como trocar x: "-100%" por opacity: 0 numa gaveta lateral.

Os oito padrões de ScrollTrigger, incluindo esse recorte responsivo, estão em ScrollTrigger: os 8 padrões.

Quatro pontos que quase todo site esquece

Rolagem suave. scroll-behavior: smooth é a omissão mais frequente, e a documentação não promete tratamento automático: a MDN registra apenas que o navegador pode ignorar a propriedade. Não conte com isso, declare a intenção:

css
@media (prefers-reduced-motion: no-preference) {
  html { scroll-behavior: smooth; }
}

Vídeo de fundo com autoplay. Não é animation nem transition, então nenhum reset de CSS o alcança. Precisa de JavaScript, ou de trocar o vídeo por um poster estático.

Transições de página. As View Transitions animam por padrão, e a mesma consulta desliga a animação preservando a troca de conteúdo. O funcionamento delas está em View Transitions em Next.js 16.

Animações scroll-driven em CSS. Rodam na thread do compositor e não passam por JavaScript, o que é ótimo para desempenho e significa que só a consulta de mídia as controla. Como elas funcionam está em CSS scroll-driven sem JS.

O que o WCAG exige, e o que não exige

Vale separar os dois critérios, porque o nível de conformidade é diferente e isso muda a prioridade.

CritérioNívelCobre
2.2.2 Pause, Stop, HideAMovimento automático que dura mais de cinco segundos, ao lado de outro conteúdo
2.3.3 Animation from InteractionsAAAAnimação de movimento disparada por interação, como parallax de rolagem

O 2.2.2 é nível A, o piso da conformidade, e exige um mecanismo de pausar, parar ou esconder. Carrossel com auto-avanço e vídeo de fundo em loop caem aqui.

O 2.3.3, nível AAA, é onde mora o parallax. Ele diz que a animação disparada por interação precisa poder ser desativada. A exceção é a mesma: animação essencial à funcionalidade ou à informação transmitida.

Duas leituras práticas.

Respeitar prefers-reduced-motion é o caminho mais curto para o 2.3.3. O sistema operacional já é o mecanismo de desativação que o critério pede.

O 2.2.2 não se resolve só com a consulta de mídia. Ele pede um controle visível na interface, para quem não ligou a preferência no sistema.

Como testar

Ligue a preferência e recarregue. Em navegador Chromium também dá para emular pelo DevTools, em Rendering, na opção de emular a mídia prefers-reduced-motion.

SistemaCaminho
Windows 11Configurações → Acessibilidade → Efeitos visuais → Efeitos de animação
Windows 10Configurações → Facilidade de Acesso → Vídeo → Mostrar animações
macOSAjustes do Sistema → Acessibilidade → Tela → Reduzir movimento
iOSAjustes → Acessibilidade → Movimento
Android 9+Configurações → Acessibilidade → Remover animações
GNOMEConfigurações → Acessibilidade → Visão → Animação reduzida

Teste a página inteira com a preferência ligada. Três perguntas: todo o conteúdo aparece? Os estados continuam legíveis? Algo ficou preso invisível?

O terceiro é o defeito mais comum de quem implementa pela primeira vez.

O contexto maior, do briefing à entrega, está no guia completo de motion design para web. E se a dúvida é qual ferramenta usar antes de chegar nesse ponto, a árvore de decisão entre GSAP, Motion e CSS resolve em três perguntas.

Perguntas frequentes

Qual a diferença entre reduce e no-preference?

reduce indica que a pessoa ligou a configuração de movimento reduzido no dispositivo. no-preference indica que nenhuma preferência foi declarada e avalia como falso quando reduce está ativo. Escrever a animação dentro de no-preference deixa o estado sem movimento como padrão, inclusive em navegador que não suporta a consulta.

Preciso desligar toda animação quando a preferência está ativa?

Não. A especificação fala em remover, reduzir ou substituir, e animação essencial à funcionalidade ou à informação tem exceção explícita nos dois critérios do WCAG. Fade de opacidade e mudança de cor não têm componente espacial e costumam ser seguros como substituição.

O scroll-behavior: smooth respeita a preferência sozinho?

A documentação não garante isso. A MDN registra que o navegador pode ignorar a propriedade, sem prometer tratamento de movimento reduzido. Coloque a declaração dentro de @media (prefers-reduced-motion: no-preference) em vez de contar com o comportamento do navegador.

Isso é obrigatório para conformidade?

Depende do nível. Animação automática que dura mais de cinco segundos cai no critério 2.2.2, nível A, que é o piso de conformidade e pede um controle de pausar, parar ou esconder. Animação disparada por interação, como parallax, cai no 2.3.3, nível AAA. Respeitar a preferência do sistema atende ao segundo e ajuda no primeiro, sem substituir o controle na interface.

Fontes

Verificado em 20 de agosto de 2026.

Gatilho de reavaliação: revisar quando (a) a especificação de mídia ganhar valor além de reduce e no-preference, (b) os navegadores passarem a documentar tratamento automático de scroll-behavior sob movimento reduzido, ou (c) o WCAG promover o critério 2.3.3 para um nível abaixo de AAA.

Leia também