Skip to content
Logic2BUI

Search docs

Search components and documentation

New
Menu

Scroll Reveal

Play a motion enter recipe when an element scrolls into view — built on IntersectionObserver and the motion engine, with zero runtime dependencies.

Scroll down ↓

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-reveal

Usage

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.

Generated JSX
<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.

View JSON

ScrollRevealProps

Type

Plays 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

Component
Props: ScrollRevealProps
PropTypeRequiredDefaultDescription
presetMotionPresetNo"fade-up"Enter recipe to play when revealed. Default `"fade-up"`.
durationnumberNo—Reveal duration in milliseconds. Overrides the 500ms default.
delaynumberNo—Reveal delay in milliseconds — the knob for staggering a group.
asChildbooleanNofalseMerge props onto the single child instead of rendering a wrapper.
oncebooleanNotrueStop observing after the first time the element enters view. Default `true`.
amountnumberNo0.3Fraction of the element that must be visible to count as in view (0–1). Default `0.3`.
marginstringNo"0px"IntersectionObserver `rootMargin`, e.g. `"-10% 0px"`. Default `"0px"`.

Accessibility contract

Machine-readable behavior and consumer responsibilities for scroll-reveal.

View JSON
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.