NEWVersion 1.0 is here.
OpenTailwindv1.0
BlocksTemplatesCollectionsGuidesPricing
Tailwind CSS Scroll Animations with Reduced Motion
BlogTailwind CSS Scroll Animations with Reduced Motion
TutorialUpdated October 14, 2026·4 min read

Tailwind CSS Scroll Animations with Reduced Motion

Try a working scroll-reveal example using IntersectionObserver and compare a Motion integration. Keep content visible and respect reduced motion.

By OpenTailwind, by lindo.ai

On this page

  1. A working reveal with no animation dependency
  2. When Motion is useful
  3. Choose what moves
  4. Keep the page stable
  5. Test the fallback, not only the effect

Use scroll animation to support the order of a page, while keeping content visible without JavaScript. A small reveal on a supporting card can help establish sequence; hiding the main headline until a library loads makes the page more fragile. Respect reduced-motion preferences and avoid making visitors wait for essential information.

A working reveal with no animation dependency

Example setup: Tailwind CSS 4.1.18, compiled locally, with plain HTML and vanilla JavaScript. These small educational examples are separate from the membership library. Adapt them to your application and test your final content and browser requirements.

Original OpenTailwind educational example. Open the full-width demo.
Complete HTML, CSS, and JavaScript

Use your compiled Tailwind stylesheet in place of /examples/tailwind.css. The example uses Tailwind CSS 4 utilities and vanilla JavaScript.

<!doctype html>
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><meta name="robots" content="noindex,follow"><title>Scroll reveals with progressive enhancement</title><link rel="stylesheet" href="/examples/tailwind.css"><style>body{font-family:system-ui,sans-serif;background:#faf9f6;color:#252824;padding:24px}button{cursor:pointer}button:disabled{cursor:wait;opacity:.6}:focus-visible{outline:3px solid #4f46e5;outline-offset:4px}</style></head><body><h1 class="text-2xl font-semibold">A little movement. A clear story.</h1><p class="mt-2 mb-8 text-stone-600">Scroll inside this example. Content stays readable without JavaScript.</p>
<div class="space-y-8"><section class="reveal rounded-2xl bg-orange-100 p-8"><h2 class="text-xl font-semibold">Start with the idea</h2><p>Make the main message easy to find.</p></section><section class="reveal rounded-2xl bg-indigo-100 p-8"><h2 class="text-xl font-semibold">Show the details</h2><p>Use movement to support the hierarchy.</p></section><section class="reveal rounded-2xl bg-emerald-100 p-8"><h2 class="text-xl font-semibold">Give it room</h2><p>Leave essential content visible.</p></section></div><script>const preference = matchMedia('(prefers-reduced-motion: reduce)');
const played = new WeakSet();
const animations = new Set();
if ('IntersectionObserver' in window && Element.prototype.animate) {
  const observer = new IntersectionObserver(entries => {
    for (const entry of entries) {
      if (!entry.isIntersecting || played.has(entry.target)) continue;
      played.add(entry.target);
      observer.unobserve(entry.target);
      if (preference.matches) continue;
      const animation = entry.target.animate([{ transform: 'translateY(16px)', opacity: 0.5 }, { transform: 'translateY(0)', opacity: 1 }], { duration: 350, easing: 'ease-out' });
      animations.add(animation);
      animation.onfinish = () => animations.delete(animation);
    }
  }, { threshold: 0.15 });
  document.querySelectorAll('.reveal').forEach(element => observer.observe(element));
  preference.addEventListener('change', () => { if (preference.matches) { animations.forEach(animation => animation.cancel()); animations.clear(); } });
}</script></body></html>

The example uses IntersectionObserver to detect entry and the browser’s animation API to animate each section once. Tailwind provides the layout and visual styles. Content is visible by default, so unsupported APIs or failed JavaScript leave a readable page. Turning on reduced motion cancels active animations.

When Motion is useful

For a project that already uses Motion, its inView and animate functions can express a similar reveal. Install the motion package in your own project and import it from a JavaScript entry point handled by your bundler. Do not paste a bare package import into a plain HTML script.

import { animate, inView } from 'motion';

const preference = matchMedia('(prefers-reduced-motion: reduce)');
const active = new Set();
const stop = inView('.reveal', element => {
  if (preference.matches) return;
  const animation = animate(element,
    { opacity: [0.5, 1], y: [16, 0] },
    { duration: 0.35, ease: 'easeOut' });
  active.add(animation);
  animation.then(() => active.delete(animation));
});
const onPreferenceChange = () => {
  if (preference.matches) {
    active.forEach(animation => animation.complete());
    active.clear();
  }
};
preference.addEventListener('change', onPreferenceChange);
// On component/page teardown:
// stop();
// preference.removeEventListener('change', onPreferenceChange);
// active.forEach(animation => animation.complete());

The embedded runnable demo uses native browser APIs; the Motion fragment illustrates an alternative integration. Check Motion’s current inView API and your framework’s cleanup lifecycle before using it.

Choose what moves

ContentSuggested approachReason
Main heading and primary actionVisible immediatelyVisitors should not wait to understand the page
Supporting cardsSmall, one-time revealA restrained transition can clarify grouping
Long article textUsually staticReading benefits from stable content
Automatically rotating contentAvoid unless needed; provide controlsMotion should not take away reading time

Keep the page stable

Animate transforms and opacity rather than repeatedly changing the document layout. Reserve image dimensions. Do not stagger a long list so heavily that the final item remains unavailable while someone scrolls. If you animate words individually, keep a coherent accessible text representation instead of making screen readers encounter fragments.

Test the fallback, not only the effect

  1. Disable JavaScript and verify the article remains readable.
  2. Enable reduced motion before loading, then change it while the page is open.
  3. Scroll quickly and check that no sections remain hidden.
  4. Use keyboard navigation to reach links inside animated sections.
  5. Check a slow device with real images and real content.

Compare this with the carousel guide before adding more movement. Explore feature layouts for sections that work well even when every animation is removed.

Library access: Tutorials and their educational demos are public. Copying library designs requires an account: 100 selected blocks and five templates per industry are free. Pro lifetime access unlocks the complete library.

Keep building

Carousel vs Slider: Differences and Accessible Examples ↗

Understand carousel and slider terminology, try a manual carousel and range input, and choose controls that work with a keyboard.

Tailwind CSS Hero Sections: 30+ Layout Examples ↗

A visual gallery of 30+ Tailwind CSS hero section designs to preview, customize, and copy with an account. Split layouts, centered, gradient, video backgrounds, and more.

Browse 1,761 Tailwind CSS blocks

100 free blocks, five free templates per industry, and Pro lifetime access to the complete block and template library.

Browse BlocksTemplates
← Previous article

Responsive Tailwind CSS Navbar: Working Menu and 5 Layouts

Next article →

Why Your Tailwind CSS Site Looks the Same as Everyone Else's (And How to Fix It)

OpenTailwindv1.0

A little less starting from scratch.
A lot more making it yours.

Made with care by lindo.ai

The library

All blocksAll templatesHero sectionsPricing sections

Around here

Our storyGuides & ideasDesign examplesMembership & pricingQuestions?

Good design is a
good place to start.

FREE TO EXPLORE. YOURS TO BUILD.
© 2026 OpenTailwind · by lindo.ai
TermsPrivacyCookies All creativity welcome