Scroll reveal
for Astro.
Zero JS by default. Native scroll-driven animations — no runtime, no bundle, just CSS.
Scroll to see it live — and back up to reverse
Why astro-reveal
The animation is the pitch.
Everything else stays out of the way.
Zero runtime JS
Purist mode uses native CSS scroll-driven animations. No IntersectionObserver, no bundle, no hydration. The browser does all the work.
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.
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.
Live gallery
Seven animations.
One attribute.
Scroll through each block. In purist mode every animation scrubs with the scroll position — reverse as you go back up.
Rises from below — the default. Great for cards, text blocks, and section entries.
Attribute
<div data-reveal="up">
<!-- your content -->
</div> Component
<Reveal animation="up">
<!-- your content -->
</Reveal> Falls from above. Useful for headers or elements that descend into view.
Attribute
<div data-reveal="down">
<!-- your content -->
</div> Component
<Reveal animation="down">
<!-- your content -->
</Reveal> Slides in from the right. Natural for timeline items and horizontal layouts.
Attribute
<div data-reveal="left">
<!-- your content -->
</div> Component
<Reveal animation="left">
<!-- your content -->
</Reveal> Slides in from the left. Mirror of left — use for alternating rows.
Attribute
<div data-reveal="right">
<!-- your content -->
</div> Component
<Reveal animation="right">
<!-- your content -->
</Reveal> Pure opacity. No movement. Best for overlays, images, and subtle reveals.
Attribute
<div data-reveal="fade">
<!-- your content -->
</div> Component
<Reveal animation="fade">
<!-- your content -->
</Reveal> Grows from slightly smaller. Dramatic on cards, buttons, and feature blocks.
Attribute
<div data-reveal="scale">
<!-- your content -->
</div> Component
<Reveal animation="scale">
<!-- your content -->
</Reveal> Sharpens into focus. Unique and editorial — great for hero subtitles.
Attribute
<div data-reveal="blur">
<!-- your content -->
</div> Component
<Reveal animation="blur">
<!-- your content -->
</Reveal> Stagger
Cascade with index.
Pass an index prop (0, 1, 2…) and each element enters with a staggered delay. Perfect for grids and lists.
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.
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)
}); 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> 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.
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)
- ✓ 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
- ✓ ~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>