Layer parallax
FreeA 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.
pnpm dlx shadcn@latest add @caliper/layer-parallaxMouse 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 #
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.
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
Layer
component
Also accepts the native props of its root element and a ref.
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.