Prism
← All primitives

Reveal

Motion

Fades + lifts children into view on scroll via IntersectionObserver. Dependency-free, respects reduced-motion.

Requires the .reveal / .reveal.in transition rules from globals.css — copy them into your global stylesheet, or children render but never animate in.

$npx shadcn@latest add https://prism.icglabs.co/r/reveal.json
View raw manifest →

components/prism/Reveal.tsx
"use client";

import { useEffect, useRef } from "react";

/**
 * Reveal — fades + lifts its children into view on scroll via IntersectionObserver.
 * Robust and dependency-free (no scroll-sync edge cases). The `.reveal/.in`
 * styling lives in globals.css and respects prefers-reduced-motion.
 */
export function Reveal({
  children,
  className = "",
  delay = 0,
}: {
  children: React.ReactNode;
  className?: string;
  delay?: number;
}) {
  const ref = useRef<HTMLDivElement>(null);

  useEffect(() => {
    const el = ref.current;
    if (!el) return;
    const io = new IntersectionObserver(
      (entries) => {
        for (const e of entries) {
          if (e.isIntersecting) {
            el.classList.add("in");
            io.unobserve(el);
          }
        }
      },
      { threshold: 0.15, rootMargin: "0px 0px -8% 0px" }
    );
    io.observe(el);
    return () => io.disconnect();
  }, []);

  return (
    <div ref={ref} className={`reveal ${className}`} style={{ transitionDelay: `${delay}s` }}>
      {children}
    </div>
  );
}
Live demo — read-only. Every section is a real, copyable primitive.