Skip to content
6

Scroll Reveal

Fades and slides content in from any direction as it enters the viewport. Configurable distance, delay and replay; respects reduced-motion.

Example

Installation

npx shadcn@latest add https://hirael.com/r/scroll-reveal.json

Usage

import { ScrollReveal } from "@/components/scroll-reveal"

Paths assume the default @/ alias from your components.json. The example above shows the parts composed.

API

<ScrollReveal />

+ native element props
PropTypeDefault
direction

Direction the content travels in from.

ScrollRevealDirection'up'
distance

Travel distance, in px.

number24
delay

Delay before the reveal starts, in ms.

number0
duration

Reveal duration, in ms.

number600
once

Reveal once and stay, or replay every time it re-enters the viewport.

booleantrue
amount

Visible fraction (0–1) that triggers the reveal.

number0.3

Component source

'use client';

import { type HTMLMotionProps, motion, useReducedMotion } from 'motion/react';

type ScrollRevealDirection = 'up' | 'down' | 'left' | 'right';

interface ScrollRevealProps extends HTMLMotionProps<'div'> {
  /** Direction the content travels in from. */
  direction?: ScrollRevealDirection;
  /** Travel distance, in px. */
  distance?: number;
  /** Delay before the reveal starts, in ms. */
  delay?: number;
  /** Reveal duration, in ms. */
  duration?: number;
  /** Reveal once and stay, or replay every time it re-enters the viewport. */
  once?: boolean;
  /** Visible fraction (0–1) that triggers the reveal. */
  amount?: number;
}

const offsetFor = (direction: ScrollRevealDirection, distance: number) => {
  switch (direction) {
    case 'up':
      return { y: distance };
    case 'down':
      return { y: -distance };
    case 'left':
      return { x: distance };
    case 'right':
      return { x: -distance };
  }
};

const ScrollReveal = ({
  direction = 'up',
  distance = 24,
  delay = 0,
  duration = 600,
  once = true,
  amount = 0.3,
  ...props
}: ScrollRevealProps) => {
  const reduced = useReducedMotion();

  if (reduced) {
    return <motion.div data-slot="scroll-reveal" {...props} />;
  }

  return (
    <motion.div
      data-slot="scroll-reveal"
      initial={{ opacity: 0, ...offsetFor(direction, distance) }}
      whileInView={{ opacity: 1, x: 0, y: 0 }}
      viewport={{ once, amount }}
      transition={{
        duration: duration / 1000,
        delay: delay / 1000,
        ease: [0.22, 1, 0.36, 1],
      }}
      {...props}
    />
  );
};

export { ScrollReveal };

Dependencies

npm

motion