savasaachi

Reveal

Component

About

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

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Reveal to your project and fits it to your theme.

Code

How Reveal is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"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>
  );
}