Skip to content
caliper ui docs

Components

Clip reveal

Uncovers an image or section with a wipe as it scrolls into view, the content settling from a slight zoom behind the edge. From any side or as an iris from the middle, once or every time.

Free
pnpm dlx shadcn@latest add @caliper/clip-reveal

Field notes

Scroll the panel: each picture is uncovered as it arrives.

Three places, three times of day. Each picture is uncovered as it scrolls in.

A mountain ridge at first light
Ridge, 06:12
Sand dunes under a pale sky
Dunes, 11:40
Tall pines in mist
Forest, 17:05
Installs components/ui/clip-reveal.tsxDepends on none beyond ReactMotion honours reduced motion

When to use

Use it for

  • Photography and case studies on marketing pages, where each picture should arrive.
  • Long editorial pages that alternate text and images.

Skip it when

  • Product UI and anything people need immediately.
  • Content above the fold; it is left alone on purpose.

Decisions

Why it is built this way, so you know what to keep when you change your copy.

Behaviour

Never hides what you can see
Only content that starts out of view is hidden, so nothing visible on load flashes away and back.
Reduced motion
Everything simply shows.

Styling

Clip, not layout
The wipe is a clip path and the zoom a transform, so the page never shifts.

Installation

terminal
pnpm dlx shadcn@latest add @caliper/clip-reveal

Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage

This is the code behind the preview above.

example.tsx
import { type ReactNode } from "react"
import { ClipReveal } from "@/components/ui/clip-reveal"

function Panel({ title, context, className = "max-w-md", children }: { title: string; context: string; className?: string; children: ReactNode }) {
  return (
    <section className={`grid w-full gap-5 rounded-overlay border border-border bg-background p-6 shadow-resting max-sm:p-4 ${className}`}>
      <div className="grid gap-1">
        <p className="font-medium">{title}</p>
        <p className="text-sm text-muted-foreground">{context}</p>
      </div>
      {children}
    </section>
  );
}

const PHOTOS = [
  { src: "/media/ridge.jpg", alt: "A mountain ridge at first light", from: "bottom" as const, caption: "Ridge, 06:12" },
  { src: "/media/dunes.jpg", alt: "Sand dunes under a pale sky", from: "left" as const, caption: "Dunes, 11:40" },
  { src: "/media/forest.jpg", alt: "Tall pines in mist", from: "iris" as const, caption: "Forest, 17:05" },
];

export function ClipRevealDemo() {
  return (
    <Panel title="Field notes" context="Scroll the panel: each picture is uncovered as it arrives." className="max-w-xl">
      <div className="h-96 overflow-y-auto rounded-surface bg-muted p-4 max-sm:h-80">
        <p className="max-w-sm pb-4 text-sm text-muted-foreground">Three places, three times of day. Each picture is uncovered as it scrolls in.</p>
        <div className="grid gap-10 pb-10">
          {PHOTOS.map((photo) => (
            <figure key={photo.src} className="grid gap-2">
              <ClipReveal from={photo.from} repeat className="aspect-[4/3] rounded-surface">
                <img src={photo.src} alt={photo.alt} className="size-full object-cover" />
              </ClipReveal>
              <figcaption className="font-mono text-xs text-muted-foreground">{photo.caption}</figcaption>
            </figure>
          ))}
        </div>
      </div>
    </Panel>
  );
}

API reference

ClipReveal

component

Also accepts the native props of its root element and a ref.

PropTypeDefaultDescription
from"left" | "right" | "top" | "bottom" | "iris""bottom"The edge the wipe starts from, or an iris that opens from the middle.
repeatbooleanfalseHides again when scrolled out of view, so the wipe plays each time.
delaynumber0Extra wait before the wipe, in ms, for staggering neighbours.

Accessibility

It is built on native elements, so roles and focus come from the browser.

No custom key handling; Tab and the native activation keys do the work.

Motion

It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.

All Media examples