Skip to content

spotbo-inc/react-stagger-reveal

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

react-stagger-reveal

npm version bundle size license TypeScript

Zero-dependency, SSR-safe scroll reveal animations for React. ~3 KB gzipped.

Built for Next.js App Router. Works with any React 18+ app.


Why?

Framer Motion is 36 KB+ gzipped. You need fade-up-on-scroll. This library is 3 KB, has zero dependencies, and is SSR-safe out of the box.

react-stagger-reveal Framer Motion AOS react-intersection-observer
Size ~3 KB 36 KB+ 14 KB ~1 KB
Zero deps Yes No No Yes
SSR safe Yes Yes No Yes
Stagger children Yes Yes No No
CSS only Yes No (JS anim) No N/A (observer only)
Animations Reveal / fade Full suite Reveal None

Install

npm install react-stagger-reveal
yarn add react-stagger-reveal
pnpm add react-stagger-reveal

Quick Start

import { StaggerOnView, FadeUp } from "react-stagger-reveal";

export function Features() {
  return (
    <StaggerOnView>
      <FadeUp><h2>Fast</h2></FadeUp>
      <FadeUp><h2>Simple</h2></FadeUp>
      <FadeUp><h2>Lightweight</h2></FadeUp>
    </StaggerOnView>
  );
}

Three lines. No configuration. Children fade up one by one as they scroll into view.


Components

<StaggerOnView>

Animate children sequentially when the container scrolls into the viewport.

<StaggerOnView margin="-100px" staggerDelay={100} initialDelay={200}>
  <FadeUp><Card /></FadeUp>
  <FadeUp><Card /></FadeUp>
  <FadeUp><Card /></FadeUp>
</StaggerOnView>
Prop Type Default Description
as ElementType "div" HTML element or component to render
margin string "-80px" IntersectionObserver rootMargin
once boolean true Only trigger once
staggerDelay number 120 ms between each child
initialDelay number 150 ms before the first child
className string -- CSS class name(s)

<StaggerOnMount>

Animate children sequentially on mount (no scroll trigger).

<StaggerOnMount>
  <FadeUp><NavItem /></FadeUp>
  <FadeUp><NavItem /></FadeUp>
</StaggerOnMount>
Prop Type Default Description
as ElementType "div" HTML element or component to render
staggerDelay number 120 ms between each child
initialDelay number 150 ms before the first child
className string -- CSS class name(s)

<FadeUp>

Child of a stagger container. Fades up with an auto-calculated delay based on its sibling index.

<StaggerOnView>
  <FadeUp as="li" distance={30} duration={500}>
    <span>Item</span>
  </FadeUp>
</StaggerOnView>
Prop Type Default Description
as ElementType "div" HTML element or component to render
distance number 20 translateY distance in px
duration number 700 Transition duration in ms
easing string "cubic-bezier(0.25, 0.1, 0.25, 1)" CSS timing function
staggerDelay number 120 Override parent stagger delay
initialDelay number 150 Override parent initial delay
className string -- CSS class name(s)

<FadeOnView>

Standalone fade-in on scroll. No stagger container needed.

<FadeOnView duration={500}>
  <img src="/hero.jpg" alt="Hero" />
</FadeOnView>
Prop Type Default Description
as ElementType "div" HTML element or component to render
duration number 600 Transition duration in ms
easing string "cubic-bezier(0.25, 0.1, 0.25, 1)" CSS timing function
margin string "-80px" IntersectionObserver rootMargin
once boolean true Only trigger once
className string -- CSS class name(s)

<FadeUpOnView>

Standalone fade-up on scroll. Combines vertical translation with opacity.

<FadeUpOnView distance={30} duration={800}>
  <section>About us</section>
</FadeUpOnView>
Prop Type Default Description
as ElementType "div" HTML element or component to render
distance number 20 translateY distance in px
duration number 700 Transition duration in ms
easing string "cubic-bezier(0.25, 0.1, 0.25, 1)" CSS timing function
margin string "-80px" IntersectionObserver rootMargin
once boolean true Only trigger once
className string -- CSS class name(s)

<DelayedFade>

Fades in after a fixed time delay. Useful after hero animations or loading states.

<DelayedFade delay={2}>
  <p>Appears after 2 seconds</p>
</DelayedFade>
Prop Type Default Description
as ElementType "div" HTML element or component to render
delay number 1.5 Delay in seconds
duration number 800 Transition duration in ms
easing string "ease" CSS timing function
className string -- CSS class name(s)

Hooks

useInView(margin?, once?)

Low-level hook that reports whether an element is in the viewport.

import { useInView } from "react-stagger-reveal";

function MyComponent() {
  const { ref, inView } = useInView("-100px");

  return (
    <div ref={ref}>
      {inView ? "Visible!" : "Not yet..."}
    </div>
  );
}
Param Type Default Description
margin string "-80px" IntersectionObserver rootMargin
once boolean true Stop observing after first hit

Returns: { ref: RefObject<HTMLElement | null>, inView: boolean }


How It Works

  1. SSR: Renders children fully visible. Crawlers and screen readers see all content immediately.
  2. Hydration: After React hydrates, elements are hidden (opacity 0, translateY offset).
  3. Trigger: IntersectionObserver detects scroll visibility, or requestAnimationFrame fires on mount.
  4. Stagger: The parent container sets a data-stagger-mounted attribute. Each FadeUp child watches for this attribute via MutationObserver and calculates its delay from its sibling index.
  5. Animate: Pure CSS transitions handle the actual animation (opacity + transform). No JavaScript animation loop. No layout thrashing.

This architecture means:

  • Content is always in the DOM (no conditional rendering, no display: none)
  • Animations run on the compositor thread (opacity + transform are GPU-accelerated)
  • The library is tree-shakeable -- import only what you use

Polymorphic as Prop

Every component accepts an as prop for semantic HTML:

<StaggerOnView as="ul">
  <FadeUp as="li">First</FadeUp>
  <FadeUp as="li">Second</FadeUp>
  <FadeUp as="li">Third</FadeUp>
</StaggerOnView>

<FadeUpOnView as="section" className="hero">
  <h1>Welcome</h1>
</FadeUpOnView>

Accessibility

  • prefers-reduced-motion: When the user has enabled reduced motion in their OS settings, all animations are skipped. Elements render immediately visible with no transitions.
  • Always in the DOM: Content is never conditionally rendered. Screen readers and crawlers always have access.
  • SSR visible: During server-side rendering, all content renders at full opacity so that non-JS environments see everything.
  • No layout shift: Animations use opacity and transform only, which do not cause layout reflow.

Next.js App Router

The package includes "use client" directives. It works out of the box with Next.js App Router -- no extra configuration needed.

// app/page.tsx (Server Component)
import { Features } from "./features";

export default function Home() {
  return <Features />;
}

// app/features.tsx (automatically a Client Component via the import)
import { StaggerOnView, FadeUp } from "react-stagger-reveal";

export function Features() {
  return (
    <StaggerOnView>
      <FadeUp><div>Feature 1</div></FadeUp>
      <FadeUp><div>Feature 2</div></FadeUp>
    </StaggerOnView>
  );
}

Browser Support

Works in all browsers that support IntersectionObserver (Chrome 51+, Firefox 55+, Safari 12.1+, Edge 15+). In older browsers, elements render immediately visible (graceful degradation, not a blank page).


License

MIT -- Spotbo, Inc.

Releases

No releases published

Packages

 
 
 

Contributors