Fade up
Revealed as it scrolls into view.
Fade left
Revealed as it scrolls into view.
Scale
Revealed as it scrolls into view.
Blur
Revealed as it scrolls into view.
Installation
npx logic2b@next add scroll-revealUsage
import { ScrollReveal } from "@/components/ui/scroll-reveal"
<ScrollReveal preset="fade-up">
<Card>…</Card>
</ScrollReveal>
ScrollReveal is the scroll-triggered counterpart to
Motion: instead of playing on mount, it waits until
the element scrolls into view (via the
useInView IntersectionObserver hook), then plays the same enter
recipe. It shares every preset — fade, fade-up/down/left/right, scale,
blur — and the same duration, delay, asChild props and
prefers-reduced-motion fallback.
It renders visible until it has mounted and armed, so search engines and clients without JavaScript always see the content — the reveal is pure progressive enhancement.
Staggering a group
Give each item an increasing delay to cascade a list as it enters:
{posts.map((post, i) => (
<ScrollReveal key={post.id} preset="fade-up" delay={i * 80}>
<PostCard {...post} />
</ScrollReveal>
))}
Revealing again on every pass
By default a reveal fires once and stays. Pass once={false} to re-hide and
replay the recipe every time the element leaves and re-enters view.
useInView
scroll-reveal installs the useInView hook, which you can use on its own to
gate anything on visibility — a lazy-loaded chart, a
useCountUp counter, an autoplaying video:
const [ref, inView] = useInView({ amount: 0.5 })
<section ref={ref}>{inView && <Chart />}</section>Live playground
Edit the component props and inspect the exact JSX before copying it. The preview loads only when this section enters the viewport.
<ScrollReveal
preset="fade-up"
duration={500}
delay={0}
once
className="rounded-lg border bg-card p-6"
>Revealed content</ScrollReveal>API reference
Generated from the public TypeScript exports in src/ui/scroll-reveal.tsx.
ScrollRevealProps
TypePlays a motion enter recipe when the element scrolls into view (rather than on mount like `<Motion>`). Built on the `useInView` IntersectionObserver hook and the motion engine's recipes, so it shares the same presets, timing and prefers-reduced-motion fallback. SSR- and no-JS-safe: it renders visible until it has mounted and armed, so search engines and scriptless clients always see the content.
interface ScrollRevealProps extends React.ComponentProps<"div">, Pick<UseInViewOptions, "once" | "amount" | "margin" | "root"> { /** Enter recipe to play when revealed. Default `"fade-up"`. */ preset?: MotionPreset /** Reveal duration in milliseconds. Overrides the 500ms default. */ duration?: number /** Reveal delay in milliseconds — the knob for staggering a group. */ delay?: number /** Merge props onto the single child instead of rendering a wrapper. */ asChild?: boolean }ScrollReveal
ComponentProps: ScrollRevealProps| Prop | Type | Required | Default | Description |
|---|---|---|---|---|
preset | MotionPreset | No | "fade-up" | Enter recipe to play when revealed. Default `"fade-up"`. |
duration | number | No | — | Reveal duration in milliseconds. Overrides the 500ms default. |
delay | number | No | — | Reveal delay in milliseconds — the knob for staggering a group. |
asChild | boolean | No | false | Merge props onto the single child instead of rendering a wrapper. |
once | boolean | No | true | Stop observing after the first time the element enters view. Default `true`. |
amount | number | No | 0.3 | Fraction of the element that must be visible to count as in view (0–1). Default `0.3`. |
margin | string | No | "0px" | IntersectionObserver `rootMargin`, e.g. `"-10% 0px"`. Default `"0px"`. |
Accessibility contract
Machine-readable behavior and consumer responsibilities for scroll-reveal.
- Support
- Composition contract
- Pattern
- scroll-triggered reveal
- Intersection Observer + Motion
Built-in semantics
- Content remains in the document reading order and preserves the rendered child semantics.
Consumer responsibilities
- Render meaningful content without requiring scroll activation and disable movement for reduced-motion preferences.
Known limitations
- • Incorrect initial styles can hide content when scripting or observation is unavailable.