caliper uidocs

Layer parallax

Free

A composition of layers that shift against each other as the mouse moves, each by its own depth, with the whole scene tilting no more than 5 degrees. It explains how a screen is built: the grid behind, the card on it, the chip and the toast in front. It is for docs and demonstration stages, never for controls or moving text.

Installs components/ui/layer-parallax.tsxDepends on @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add @caliper/layer-parallax
Weekly signups
Mon to Sat
+12%
Deploy finished

Mouse only. Touch, keyboard and reduced motion see the still composition.

When to use #

Use it for

  • Docs and landing sections that show the layers of a screen or a component.
  • A demonstration stage that needs a little life without autoplay.

Skip it when

  • Anything people must click or read while it moves.
  • Hero art for its own sake; a still image says the same.

Decisions #

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

Behaviour

Mouse only
Touch, keyboard and reduced motion see the still composition, which is the complete picture. Nothing here takes focus.

Styling

Small, capped movement
A layer shifts at most 16px and the scene tilts at most 5 degrees, so text stays flat to read. A switch and a slider let the viewer turn it down or off.

Usage

Layer takes a depth
0 stays put, 1 shifts by the full intensity. You place each layer with className or style, or pass fill for a backdrop.

Installation #

terminal
pnpm dlx shadcn@latest add @caliper/layer-parallax

Adds its 9 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. It is built from LayerParallax and Layer.

example.tsx
import { Layer, LayerParallax } from "@/components/ui/layer-parallax"

export function LayerParallaxDemo() {
  return (
    <div className="w-full max-w-xl">
      <LayerParallax label="Layers of a dashboard screen">
        <Layer depth={0} fill className="opacity-70 bg-[linear-gradient(var(--border)_1px,transparent_1px),linear-gradient(90deg,var(--border)_1px,transparent_1px)] bg-[size:28px_28px]" />
        <Layer depth={0.4} className="left-[10%] top-[18%] grid h-[62%] w-[60%] content-start gap-2 rounded-[14px] border border-border-strong bg-elevated p-3.5">
          <b className="text-[13px]">Weekly signups</b>
          <div className="flex h-[84px] items-end gap-1.5 border-b border-border-strong">
            {[40, 55, 48, 70, 92, 64].map((h, i) => (
              <i key={i} className={`flex-1 rounded-t-[3px] ${i === 4 ? "bg-brand" : "bg-foreground"}`} style={{ height: `${h}%` }} />
            ))}
          </div>
          <small className="font-mono text-muted-foreground">Mon to Sat</small>
        </Layer>
        <Layer depth={0.8} className="right-[8%] top-[14%] rounded-full border border-border-strong bg-elevated px-2.5 py-1.5 font-mono text-xs font-semibold">
          +12%
        </Layer>
        <Layer depth={1.2} className="bottom-[12%] right-[6%] flex items-center gap-2 rounded-xl border border-border-strong bg-elevated px-3 py-2.5 text-[12.5px]">
          <i className="size-2 rounded-full bg-success" />
          Deploy finished
        </Layer>
      </LayerParallax>
    </div>
  );
}

API reference #

LayerParallax

component
PropTypeDefaultDescription
label *string—Accessible name of the composition.
children *ReactNode—`Layer` children, back to front.
intensitynumber—Largest shift in pixels for a layer at depth 1, 0 to 16. Controlled when set.
defaultIntensitynumber10
onIntensityChange((intensity: number) => void)—
enabledboolean—Whether the pointer moves the layers. Controlled when set.
defaultEnabledbooleantrue
onEnabledChange((enabled: boolean) => void)—
controlsbooleantrueShow the switch and the intensity slider.
statusDepthStatus"ready"
messagesDepthMessages—
onRetry(() => void)—
disabledboolean—
classNamestring—

Layer

component

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

PropTypeDefault
depth *number—
fillboolean—

Accessibility #

It exposes group semantics, with aria-label and aria-valuetext kept in step with state.

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

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.