Mischief

76 / Motion

Reveal

Moves its children in when they arrive on screen. It changes how something arrives and never whether it is there.

Scroll down
It waits until it is on screen
Then it arrives, one after another
And it stays where it landed

Installation

Copy the source into your project, or keep it behind a package.

npx shadcn@latest add Tinkerers-Labs/mischief-ui/reveal
import { Reveal } from "mischief-ui/reveal"

Or paste it in yourself. The source imports the shared cn helper from @/lib/utils, so point that at your own copy.

registry/default/reveal/reveal.tsx
"use client" import * as React from "react"import { cn } from "@/lib/utils" export type RevealDirection = "up" | "down" | "left" | "right" | "none" export type RevealProps = React.HTMLAttributes<HTMLDivElement> & {  /** Which way the content travels in from. */  from?: RevealDirection  /** Pixels travelled. */  distance?: number  /** Milliseconds before it starts. Give a list an index times a step. */  delay?: number

Usage

export function Features({ items }) {
  return items.map((item, index) => (
    <Reveal key={item.id} delay={index * 90}>
      <Card {...item} />
    </Reveal>
  ))
}

The content is never withheld

Scroll entrances are usually built by rendering nothing until an observer fires. That breaks the page for anyone whose browser did not run the observer, hides the text from anything reading the markup, and leaves a blank column if a script fails.

Here the children are always rendered and always in the document. Only opacity and a small translation are animated, and both are removed outright under reduced motion, where the content is simply there from the first paint.

Staggering without another component

There is no group wrapper, because a group wrapper would only be multiplying an index by a number. Do that where you have the index.

{rows.map((row, index) => (
  <Reveal key={row.id} delay={index * 90}>{row.label}</Reveal>
))}
Around 60 to 120 milliseconds per step reads as a sequence. Much more and the last one feels late.

API

from"up" | "down" | "left" | "right" | "none"Which way the content travels in from. Defaults to "up".
distancenumberPixels travelled. Defaults to 16.
delaynumberMilliseconds before it starts. An index times a step staggers a list.
durationnumberMilliseconds. Defaults to 600.
thresholdnumberHow much has to be on screen before it starts. Defaults to 0.15.
repeatbooleanPlays again whenever it comes back. Off by default.

Accessibility

Content is in the document and in normal order from the first paint, so nothing depends on the animation having run. Reduced motion removes the movement and the fade entirely rather than shortening them. Once played it stays played, unless repeat is on.