Skip to content

Resources / Guide · 2026-09-12

An AOS alternative for Astro

AOS keeps animated elements invisible until its script runs, shows elements without animating them after a ClientRouter navigation, and has had no release since October 2018. Reveal does the same job without those problems.

3 min readThis page as MarkdownSource on GitHub

How AOS behaves in an Astro project

AOS (Animate On Scroll) is a common choice for scroll reveals, with about 220,000 npm downloads a week in September 2026. Three of its behaviours matter on an Astro site. The measurements below use AOS 2.3.4, its current release, on Astro 7.3.2 in Chromium, Firefox and WebKit.

  • Animated elements stay invisible until the script runs. AOS’s stylesheet gives every fade and zoom element opacity: 0, and only the script reveals it. With JavaScript turned off, or with the AOS script blocked, a data-aos="fade-up" heading stayed at opacity: 0. This was reported in 2016 (michalsnik/aos#81).
  • After a <ClientRouter /> navigation, elements appear without animating. AOS still finds the new elements, because it watches the DOM for them. But AOS.init() writes the default duration, easing and delay as data-aos-* attributes on <body>, and the router swaps in a new <body> without them. The transition-duration was 0.4s on the first page and 0s on every page after it. Calling AOS.init() again on astro:page-load brings the duration back and adds another scroll and resize listener to window on each navigation: four of each after three navigations. Sites without the router are not affected.
  • No release since October 2018. Versions 2.3.4 and 3.0.0-beta.6 were both published on 3 October 2018 (npm), and the default branch has no commit after that day (history). AOS has no reduced-motion handling of its own, and an issue asking the README to show how to turn it off for prefers-reduced-motion has been open since August 2020 (michalsnik/aos#611).

Reveal, the replacement

---
import Reveal from '@moonarc/core/Reveal';
---
<Reveal><h2>Appears once, stays.</h2></Reveal>
<Reveal from="left" distance={40} delay={80}><p>Direction, distance, delay.</p></Reveal>
<Reveal as="ul" stagger={60}>
  <li>one</li><li>two</li><li>three</li>
</Reveal>
<Reveal scrub><p>Progress follows the scroll position.</p></Reveal>
  • The server HTML is the end state. Content is hidden only behind html[data-ma-js], which the integration sets just before first paint. Without JavaScript the page stays visible.
  • It binds through the shared runtime: every instance is observed after every navigation, observers are disconnected before the swap, and each element is bound exactly once, however often the router’s events fire.
  • Reduced motion keeps a fast fade and drops the travel.
  • scrub uses animation-timeline: view() where the browser has it and an observer-driven fallback elsewhere, so it works everywhere.

Cost, measured on /compare/ with one reveal per page, gzip on both sides: a page with one Reveal loads 1.2 kB gzip of JavaScript and 1.7 kB gzip of CSS, and a page with AOS 2.3.4 loads 4.8 kB gzip of JavaScript and 2.2 kB gzip of CSS.

Migration table

AOS Reveal
data-aos="fade-up" <Reveal> (default)
data-aos="fade-left" from="right"
data-aos="zoom-in" scale={0.96}
data-aos-delay="100" delay={100}
data-aos-duration="600" duration={600}
data-aos-once="false" once={false}
data-aos-offset="120" rootMargin="0px 0px -120px 0px"
data-aos-anchor-placement threshold={0.2}

See the Reveal page for every prop and the measured cost.

Animation libraries for Astro, measured builds the same scroll reveal with AOS, GSAP, Motion, AstroAnimate, astro-reveal and Reveal, and records the JavaScript each loads and what each does after a ClientRouter navigation, in Chromium, Firefox and WebKit.