Something new is coming.Join the waitlist

Reveal on Scroll

PreviousNext

React scroll reveal wrapper. Fades and slides children into view on scroll. Direction, delay, easing, and replay are configurable per instance. Built with Framer Motion.

Type-safe events

Zod-validated event schemas, no untyped trackEvent calls.

Server components first

Built for the App Router. Client islands only where needed.

Theme-aware

Every component respects --background, --foreground, and --primary tokens.

Installation

npx shadcn@latest add https://tentui.com/r/reveal-on-scroll.json

Usage

Wrap any block of content. The wrapper holds it at opacity: 0 plus a small offset until the element becomes ~20% visible, then animates to its resting position.

import { RevealOnScroll } from "@/components/reveal-on-scroll";
 
export function FeatureSection() {
  return (
    <RevealOnScroll>
      <h2>Why tent ui</h2>
      <p>Production-ready components with sensible defaults.</p>
    </RevealOnScroll>
  );
}

Patterns

Stagger a list

Compose individual delays for a clean cascade. Keep the delay step small (≤ 100 ms) so the section doesn't feel laggy on slow connections.

{features.map((feature, i) => (
  <RevealOnScroll key={feature.title} delay={i * 0.08}>
    <FeatureCard {...feature} />
  </RevealOnScroll>
))}

Direction

Pick the direction the content slides in from. none keeps the fade but skips the translate — useful for elements that already have their own subtle motion.

<RevealOnScroll direction="left">{leftColumn}</RevealOnScroll>
<RevealOnScroll direction="right">{rightColumn}</RevealOnScroll>
<RevealOnScroll direction="none">{calmHero}</RevealOnScroll>

Replay on re-enter

Default behaviour is reveal-once. Set repeat for content that should re-animate every time it scrolls back in, e.g. a horizontally-scrolled gallery.

<RevealOnScroll repeat>…</RevealOnScroll>

Props

PropTypeDefaultDescription
direction"up" | "down" | "left" | "right" | "none""up"Direction the content slides in from.
delaynumber0Delay before the reveal starts, in seconds.
durationnumber0.6Animation duration in seconds.
staggerChildrennumberStagger gap, in seconds, applied to direct child motion components if you nest them.
amountnumber0.2Fraction of the element that must be visible before triggering (0–1).
repeatbooleanfalseReplay the animation each time the element re-enters the viewport.
easeEasing"easeOut"Easing curve passed straight to motion.
classNamestringForwarded to the inner motion.div.

All other motion.div props pass through, so you can override initial, animate, or transition at the call site if you need to.

Reduced motion

useInView itself doesn't read prefers-reduced-motion. If reduced-motion users should skip the animation, gate it at the call site:

"use client";
 
import { useReducedMotion } from "motion/react";
import { RevealOnScroll } from "@/components/reveal-on-scroll";
 
export function Section({ children }: { children: React.ReactNode }) {
  const reduce = useReducedMotion();
  if (reduce) return <div>{children}</div>;
  return <RevealOnScroll>{children}</RevealOnScroll>;
}