You've seen it on every marketing site, SaaS landing page, and portfolio built in the last five years. You scroll down the page and content fades in from below, slides in from the side, or materializes out of thin air as it enters the viewport. Sometimes with a staggered delay, so each element pirouettes in one after another like a chorus line. The developers who built it think it looks 'polished.' The users who encounter it just want to read the page.
Scroll-triggered fade-in animations — what some developers call 'scroll reveal' — have become one of the most ubiquitous patterns on the web. They're also one of the most user-hostile. They slow down perceived performance, break accessibility, interfere with content scanning, and add nothing to the user's understanding of the page. It's time to stop using them.
The Case Against Scroll Fade
The problems with scroll fade aren't subtle. They're fundamental.
Content is invisible until you scroll to it. This sounds obvious, but think about what it means. If a user scrolls quickly to find a specific section — a pricing table, a feature comparison, a technical specification — they see a blank page until the animations catch up. The content is there in the DOM but hidden behind opacity: 0 and a CSS transition that hasn't fired yet. Fast scrollers see nothing. Users who use Find-in-page (Ctrl+F) land on invisible text. Users with certain assistive technologies may never trigger the scroll events at all.
It punishes fast readers. Staggered fade-ins force the user to wait for each element to appear sequentially, regardless of how fast they can consume the content. A skilled reader scanning a page for relevant information is artificially slowed to the pace of the animation. A 300ms delay per element across a section with five items means 1.5 seconds of forced waiting — an eternity when you're trying to evaluate a product.
It breaks the back button. Navigate to a page, scroll halfway down, click a link, hit back. On many scroll-fade implementations, you're returned to the page with all previously-revealed content invisible again, requiring you to re-scroll through content you've already seen just to trigger the animations again.
The Performance Tax
Scroll-fade animations have a real performance cost, even when 'optimized.' The Intersection Observer API is better than scroll event listeners, but the pattern still requires JavaScript to run, evaluate intersection ratios, and mutate DOM elements on scroll. On a page with 50 fade-in elements, that's 50 observers firing as the user scrolls.
// The "modern" approach — still problematic
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
// Each of these triggers a style recalculation
// and potentially a layout/paint operation
}
});
}, { threshold: 0.1 });
// Observing 50+ elements on a landing page
document.querySelectorAll('.fade-in').forEach(el => {
observer.observe(el);
});
// Plus the CSS:
// .fade-in { opacity: 0; transform: translateY(20px); transition: 0.6s; }
// .fade-in.visible { opacity: 1; transform: translateY(0); }
The bigger performance issue is Cumulative Layout Shift (CLS). Elements that start at opacity: 0 with a translateY offset and then animate to their final position can cause layout shifts if not handled carefully. Google's Core Web Vitals penalize this. Many scroll-fade implementations contribute to CLS scores that hurt search rankings — the exact opposite of what marketing sites want.
And there's the initial render: content starts hidden. If your JavaScript fails to load, is blocked by an ad blocker (which sometimes catches animation libraries), or throws an error, the content stays invisible forever. Your 'polished' landing page is now a blank page.
The Accessibility Damage
Users with vestibular disorders — a group that includes roughly 35% of adults over 40 — can experience dizziness, nausea, or disorientation from motion effects. The prefers-reduced-motion media query exists specifically to address this, but many scroll-fade implementations ignore it entirely.
/* The bare minimum, which most sites skip */
@media (prefers-reduced-motion: reduce) {
.fade-in {
opacity: 1 !important;
transform: none !important;
transition: none !important;
}
}
Even when prefers-reduced-motion is respected, there are deeper accessibility issues. Screen readers navigate by headings, landmarks, and links — not by scrolling. If content is hidden until a scroll event fires, screen reader users may encounter invisible elements, or the reader may announce content that the sighted user can't see yet, creating a confusing disconnect for users who rely on both audio and visual feedback.
Keyboard navigation is similarly affected. Tab through a page with scroll-fade elements and you'll focus on invisible links and buttons. The focus outline appears on a transparent element. The user has no idea what they've focused on.
Why the Pattern Persists
If scroll fade is this problematic, why is it everywhere? Three reasons.
First, it looks good in demos. When a designer or developer shows a landing page to stakeholders, they scroll slowly through the page while everyone watches. The animations feel intentional and premium. But demos aren't how users browse. Users scroll fast, jump around, use search, navigate by headings. The demo scenario — slow, linear, first-visit scrolling — is the least common real usage pattern.
Second, every competitor does it. Scroll fade has become a signifier of 'modern web design.' Not using it makes your site look static, which stakeholders interpret as outdated. This is pure cargo-culting — the animations don't serve a function, they serve an aesthetic expectation created by other sites that also don't need them.
Third, template and component libraries include it by default. Framer Motion, AOS, ScrollReveal, GSAP ScrollTrigger — these libraries make scroll animations trivially easy to add. When adding an animation is a one-line attribute, it gets added everywhere. The cost of implementation is so low that nobody stops to ask whether the animation serves the user.
When Animation Actually Helps
Motion isn't inherently bad. Animation is a legitimate UI tool when it serves one of these purposes:
- Showing cause and effect. A button press that triggers a panel to slide in communicates the relationship between the action and the result. This helps the user build a mental model of the interface.
- Directing attention. A subtle pulse on a notification badge draws the eye to new information. This is proactive — the system directing attention — rather than reactive (waiting for scroll).
- Maintaining spatial context. Animated transitions between views (like iOS's navigation push/pop) help users understand where they are in a hierarchy. Without the animation, the change feels jarring.
- Providing feedback. A loading spinner, a progress bar, a skeleton screen — these tell the user that something is happening. They reduce perceived wait time.
Notice what all of these have in common: the animation communicates information. It helps the user understand the interface. Scroll fade does neither — it's purely decorative. The content appearing from below doesn't tell the user anything about the content's relationship to other elements or its importance. It's motion for motion's sake.
What to Do Instead
If you want your pages to feel dynamic without the downsides of scroll fade, here are approaches that actually work.
Just show the content. This is the best option 90% of the time. Content that's immediately visible is content that can be read, searched, and scanned. A well-designed layout with good typography, clear hierarchy, and thoughtful spacing looks better than a page full of animations, and it loads faster, is more accessible, and doesn't break on JavaScript failure.
Use CSS scroll-driven animations. The new CSS animation-timeline: scroll() and animation-timeline: view() properties let you create scroll-linked effects without JavaScript. They're GPU-accelerated, don't cause layout shifts, and degrade gracefully. If you must have scroll-linked motion, this is the right way to do it.
/* CSS scroll-driven animation — no JavaScript needed */
.parallax-bg {
animation: parallax linear;
animation-timeline: scroll();
}
@keyframes parallax {
from { transform: translateY(0); }
to { transform: translateY(-50px); }
}
/* Respects user preferences automatically */
@media (prefers-reduced-motion: reduce) {
.parallax-bg {
animation: none;
}
}
Animate on interaction, not on scroll. Hover effects, click responses, focus states — these are triggered by user intent and provide direct feedback. They feel responsive rather than theatrical. A card that subtly lifts on hover communicates 'this is interactive.' A card that fades in on scroll communicates nothing.
Use transitions for state changes. When content genuinely appears due to a user action — expanding an accordion, opening a modal, filtering a list — animate that transition. It shows cause and effect. This is fundamentally different from content that was always there but artificially hidden until you scrolled to it. As anyone who's dealt with common UX antipatterns knows, the distinction between functional and decorative animation matters enormously.
Kill Your Darlings
The hardest part of removing scroll fade isn't technical — it's convincing stakeholders (or yourself) that the page looks better without it. We've been conditioned to associate motion with polish. A static page feels unfinished compared to one with scroll animations, even though the static page is faster, more accessible, and easier to use.
Try this exercise: take a scroll-fade-heavy page and disable all the animations. Just show everything at once. Then ask: is any information lost? Is any relationship between elements less clear? Is the page harder to understand? The answer is almost always no. The animations were decoration. The content stands on its own.
If your content needs an animation to feel compelling, the problem isn't the lack of animation — it's the content. Fix the content.