observer mode — animations play once and stay. No scroll scrubbing.

↔ See scroll (purist) mode
Observer mode · IntersectionObserver

Scroll reveal
for Astro.

IntersectionObserver mode. Plays once and stays. ~0.6 KB runtime, full browser support.

$ npm install astro-reveal

Scroll to see it live — animations play once and stay

Why astro-reveal

The animation is the pitch.
Everything else stays out of the way.

scroll-driven

Zero runtime JS

Purist mode uses native CSS scroll-driven animations. No IntersectionObserver, no bundle, no hydration. The browser does all the work.

any framework

UI-agnostic

Drop <Reveal> around any element, or use data-reveal on raw HTML. Works with React, Vue, Svelte, Solid, or no framework at all.

a11y built-in

Accessible by design

prefers-reduced-motion is respected at the CSS level — users who opt out of motion see static content with no layout shift.

Stagger

Cascade with index.

Pass an index prop (0, 1, 2…) and each element enters with a staggered delay. Perfect for grids and lists.

First index=0
Second index=1
Third index=2
Fourth index=3
Fifth index=4
Sixth index=5

How it's written

import Reveal from "astro-reveal/Reveal.astro";

{items.map((item, i) => (
  <Reveal animation="up" index={i}>
    <Card {...item} />
  </Reveal>
))}

Quickstart

Up in three steps.

01

Install & configure

Add astro-reveal to your project and register the integration.

Terminal

npm install astro-reveal

astro.config.ts

import { defineConfig } from "astro/config";
import reveal from "astro-reveal";

export default defineConfig({
  integrations: [reveal()], // default: purista (zero JS)
});
02

Use the component

Wrap any content in <Reveal>. The animation prop controls direction.

MyPage.astro

---
import Reveal from "astro-reveal/Reveal.astro";
---

<Reveal animation="up">
  <h2>This enters from below.</h2>
</Reveal>

<Reveal animation="left" index={1}>
  <p>This slides in from the right, staggered.</p>
</Reveal>
03

Or use the raw attribute

No import needed. Works on any element, any framework's output.

Any HTML

<!-- On a plain HTML element -->
<div data-reveal="fade">
  <img src="photo.jpg" alt="..." />
</div>

<!-- Inside a React / Vue / Svelte component output -->
<article data-reveal="scale">
  <slot />
</article>

AI prompt

Works with your AI, too.

Paste this into Cursor, Claude, ChatGPT, or any coding assistant. It covers the full API, the gotchas, and the best practices — so your AI doesn't have to guess.

astro-reveal integration prompt
Integrate the astro-reveal library into this Astro project.

What it is: scroll reveal animations for Astro. Two engines, one API. The default
("scroll") uses native CSS scroll-driven animations and ships NO JavaScript to the
client. The "observer" mode uses IntersectionObserver (~0.6KB), plays once and stays;
"auto" uses native where supported and falls back to observer where it isn't.

Steps:
1. Install the package: `npm install astro-reveal`
2. Add the integration in astro.config (mode "scroll" is the default):
     import reveal from "astro-reveal";
     export default defineConfig({ integrations: [reveal()] });
   No need to import any CSS manually; the integration injects it for you.
3. Animate elements in either of two equivalent ways:
   - Component: import Reveal from "astro-reveal/Reveal.astro";
       <Reveal animation="up">…</Reveal>
       Props: animation ("up"|"down"|"left"|"right"|"fade"|"scale"|"blur"),
       distance, index (for stagger), duration, as (tag to render).
   - Raw attribute (works on any HTML, whether it comes from React/Vue/Svelte or plain):
       <div data-reveal="fade">…</div>

Rules and best practices:
- Animate sections/blocks (hero, cards, features), NOT every paragraph of dense text.
- The animation name is the direction the element TRAVELS as it appears:
  "up" starts below and rises into place.
- For staggering a list, pass an incrementing index:
  items.map((x, i) => <Reveal index={i}>…</Reveal>).
- Fine-tune per element with CSS vars in style: --reveal-distance, --reveal-scale,
  --reveal-blur, --reveal-duration, --reveal-easing, --reveal-index, --reveal-stagger.
- The "scroll" mode is scrubbed: the animation reverses as you scroll back up. If you
  want "appear once and stay", use reveal({ mode: "observer" }).
- Accessibility (prefers-reduced-motion) is already handled by the library.
- CSS vars must be set on the SAME element that has data-reveal (or via the matching
  <Reveal> prop) — not on a parent/wrapper. astro-reveal declares its defaults directly
  on every [data-reveal] element, so a value set on an ancestor is shadowed by the
  element's own value and has no effect.

Do not re-implement animations by hand: use the package's API.

Modes

Purist or Observer.
You pick once, per build.

The mode is a global config — one engine per site. You cannot mix them in the same build. Choose based on your tradeoffs.

Purist (default)

mode: "scroll"
  • Zero runtime JS — pure CSS
  • Scrubs with scroll position (reverses on scroll-up)
  • No hydration, no bundle impact
  • Requires browser support for animation-timeline
  • No "play once and stay" behaviour
// astro.config.ts
import reveal from "astro-reveal";

integrations: [reveal()]
// or explicitly:
integrations: [reveal({ mode: "scroll" })]

Observer

← you're here mode: "observer"
  • ~0.6 KB gzipped runtime
  • Plays once and stays — great for dashboards
  • Full browser support via IntersectionObserver
  • Ships a tiny JS bundle
  • Does not scrub with scroll
// astro.config.ts
import reveal from "astro-reveal";

integrations: [reveal({ mode: "observer" })]

Browser support — purist mode

Scroll-driven animations (animation-timeline: scroll()) are supported in Chrome 115+, Edge 115+, and Firefox 110+ (with flag). Safari 18+ added support. Check caniuse.com/css-animation-timeline for current data. If you need broader support today, use observer mode.

Customization

Tune with CSS vars.

Override on :root for global changes, or inline on individual elements. No JS required.

Variable Description
--reveal-distance Travel distance for directional animations (up/down/left/right)
--reveal-scale Starting scale for the scale animation
--reveal-blur Starting blur amount for the blur animation
--reveal-duration Animation duration (observer mode only)
--reveal-easing Easing function
--reveal-index Stagger position — set via the index prop
--reveal-stagger Stagger delay increment per index step

Global override

:root {
  --reveal-distance: 3rem;   /* bigger travel */
  --reveal-easing: ease-out; /* simpler curve */
  --reveal-stagger: 100ms;   /* slower cascade */
}

Inline override

<div data-reveal="up" style="--reveal-distance: 4rem; --reveal-stagger: 120ms">
  <!-- travels further, stagger is slower -->
</div>