Components
Scroll-triggered reveal wrapper - IntersectionObserver fade/slide entrance with stagger delays, a pop variant, and prefers-reduced-motion support

"use client";
import * as React from "react";
import { Reveal } from "@/components/ui/reveal";
export default function RevealDemo() {
// Remounting the tree replays the entrance, so the preview is never a static
// frame (and never a blank one) whatever the scroll position.
const [run, setRun] = React.useState(0);
return (
<div className="w-full bg-background text-foreground p-8 sm:p-12">
<div className="mx-auto flex max-w-xl flex-col gap-8">
<div className="flex items-center justify-between gap-4">
<p className="text-xs font-medium uppercase tracking-widest text-muted-foreground">
Scroll reveal
</p>
<button
type="button"
onClick={() => setRun((n) => n + 1)}
className="rounded-md border border-border bg-card px-3 py-1.5 text-xs font-medium text-foreground transition-colors hover:bg-accent hover:text-accent-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:ring-offset-background"
>
Replay
</button>
</div>
<div key={run} className="flex flex-col gap-8">
<div className="rounded-xl border border-border bg-card p-6 sm:p-8">
<Reveal>
<h2 className="text-2xl font-semibold tracking-tight text-card-foreground sm:text-3xl">
Content that arrives on cue
</h2>
</Reveal>
<Reveal delay={120}>
<p className="mt-3 text-sm leading-relaxed text-muted-foreground">
Each block fades and slides up the first time it enters the
viewport. An IntersectionObserver does the work, so there is no
scroll listener and no layout thrash.
</p>
</Reveal>
<div className="mt-6 flex flex-wrap gap-2">
{["Observer-based", "No dependencies", "SSR safe"].map(
(label, i) => (
<Reveal key={label} pop delay={300 + i * 90}>
<span className="inline-flex items-center rounded-full bg-primary px-3 py-1 text-xs font-medium text-primary-foreground">
{label}
</span>
</Reveal>
),
)}
</div>
</div>
<ul className="flex flex-col gap-2">
{[
"Stagger siblings with the delay prop",
"Switch to a spring-ish pop entrance",
"Respects prefers-reduced-motion automatically",
].map((line, i) => (
<Reveal
key={line}
as="li"
delay={i * 130}
className="rounded-lg border border-border bg-card px-4 py-3 text-sm text-card-foreground"
>
{line}
</Reveal>
))}
</ul>
</div>
</div>
</div>
);
}