Why scroll animations hide content from some visitors

Scroll animations that respect “reduce motion” can leave whole sections invisible forever. The site looks perfect to everyone testing it.

Londynn Metten6 min readMistake post
Outcome
A site that still shows its content when animation is switched off
Time
About 15 minutes
Cost
Free
Level
Advanced

Short answer

If your site fades sections in as you scroll, check it with “reduce motion” turned on. A reveal wrapper that branches on the reduced-motion setting can leave its content stuck at zero opacity permanently, so entire sections never appear. We found it live on a site in July 2026 with eight blocks invisible, including the footer and the main call to action. It’s invisible in normal testing, because the setting you need to reproduce it is off on almost every machine that builds and reviews the site.

01

Who does this actually affect?

Anyone browsing with “reduce motion” switched on. That setting exists for people with vestibular disorders and migraine triggers, where a page that slides and fades can genuinely make someone ill. It’s also switched on by plenty of people who just find motion distracting, or who turned it on once to save battery and never turned it back.

You can’t see them in your analytics, and they won’t email you. Someone who lands on a page where the content never appears assumes the site is broken and leaves, which looks identical to a bounce. It fails the same way a broken contact form does, quietly and only for other people.

02

What did it look like when we found it?

A live client site, July 2026. With reduce motion on, eight blocks never appeared: About, Services, Areas, Testimonials, FAQ, the call to action, and the entire footer.

Not slow. Not glitchy. Blank, permanently. Scrolling didn’t help, waiting didn’t help, and the content was sitting right there in the page’s HTML the whole time at zero opacity.

The site was perfect on every machine anyone had tested it on, because none of those machines had the setting turned on.

03

What actually causes it?

It comes from an interaction between three reasonable things: server rendering, an animation library, and a considerate branch in the code.

The reveal wrapper asks whether the visitor prefers reduced motion. On the server that question has no answer yet, so it comes back false, and the server renders the element with the animation library’s starting style baked in: opacity zero, waiting to be faded in.

Then the page reaches the browser, the answer flips to true, and the component returns a different kind of element. An animated div becomes a plain one. React sees the same position in the tree, reuses the existing DOM node, and updates the properties it knows about. But that opacity was written straight into the element’s style by the animation library, not passed as a property React is tracking. So nothing ever clears it.

The branch that was there to protect reduced-motion visitors is the exact thing that breaks the page for them.

04

How do you check your own site in two minutes?

Turn the setting on, then hard reload. Both steps matter, because the bug happens during the initial render and you won’t reproduce it by toggling mid-session.

  1. 01On a Mac: System Settings, Accessibility, Display, then switch on Reduce motion. On Windows: Settings, Accessibility, Visual effects, then turn off Animation effects.
  2. 02Hard reload your site so it renders fresh with the setting active.
  3. 03Scroll the whole page, slowly, all the way to the bottom of the footer.
  4. 04Look for anything that never showed up. Whole sections, images, the footer, a call to action.
  5. 05Turn the setting back off when you’re done, unless you like it, in which case keep it.

If everything appears, you’re fine. If something is missing, it’s almost certainly this.

05

How do you check it automatically?

Worth doing if you ship regularly, because this comes back the moment someone adds a new reveal wrapper. The trick is to compare two runs rather than eyeball one: load the page with reduced motion on and again with it off, scroll to the bottom in both, then count the elements still sitting at near-zero opacity.

// Run the page twice, once per motion preference.
// After scrolling to the bottom, count what's still invisible:
const stranded = [...document.querySelectorAll('div')].filter(el => {
  const style = getComputedStyle(el)
  return parseFloat(style.opacity) < 0.9 && el.getBoundingClientRect().height > 100
}).length
The comparison that finds it: normal resolving to zero stranded elements while reduce stays above zero is the bug.

A healthy page settles at zero stranded elements in both runs. If the normal run reaches zero and the reduced-motion run doesn’t, you’ve found it, and the count tells you roughly how many blocks are affected.

06

What’s the fix?

Two rules, and the first one does most of the work.

  1. 01Render the same kind of element on both paths. Don’t swap an animated component for a plain one. Keep the animated component and set its transition duration to zero for reduced motion, so the content arrives instantly instead of arriving differently.
  2. 02Keep the starting style identical on the server and in the browser. It gets written into the HTML, so varying it by the motion preference creates a mismatch React won’t repair, which is a second version of the same problem.

Reduced motion should mean the animation is instant. It should never mean a different component tree.

07

What this covers, and what it doesn’t

The specific mechanism here needs three ingredients together: server-side rendering, an animation library that writes styles directly onto elements, and a branch that changes what gets rendered based on the motion preference. Take away any one and you probably don’t have this bug. That combination is extremely common in modern site builds, which is why it’s worth two minutes to rule out.

The manual check catches this class of failure and nothing else. It isn’t an accessibility audit. Contrast, keyboard navigation, headings, alt text and focus order are all separate work, and a page can pass this check while failing badly on all of them.

One more honest note: respecting reduced motion is genuinely the right thing to do. The lesson here isn’t to stop honoring it. It’s that any code path you don’t test is a path where anything can be true, and safety branches are the least-tested code in most projects precisely because they feel virtuous.

Common questions

How many people actually browse with reduced motion on?
More than most teams assume, and we won’t quote you a number because the honest answer is that it varies a lot by audience and it isn’t reliably measurable in standard analytics. The relevant number is different: if a section is invisible, the conversion rate for those visitors is zero.
Can I just remove the scroll animations?
You can, and on a content-heavy site it’s often a genuine upgrade. But you don’t have to choose. Keeping the animation and making it instant for reduced-motion visitors is a few characters of change and keeps the effect for everyone else.
Why didn’t testing catch this?
Because reduce motion is off by default and nobody building or reviewing the site had it on. It’s not a browser you forgot to check, it’s a setting, and settings aren’t part of most test matrices. That’s exactly why it’s worth adding to yours.
Does this affect my search ranking?
Not directly, since crawlers don’t express a motion preference and the content is present in the HTML. The cost is entirely on the human side, which in practice is where the money is anyway.
I use a website builder, not custom code. Do I need to worry?
Much less. This lives in custom animation code. Still worth the two-minute check if your theme animates things in as you scroll, because plugins and themes have their own versions of this idea.

Turn on reduce motion, reload your site, and scroll to the bottom. Two minutes to know for certain.

Get Unstuck