crucıble

Shingle

A scroll-pinned card stack: full-width cards slide up and seat at a fixed point with a 2px settle-and-lock, then pile into a receding deck as later cards pin over them — depth carried by cool desaturation, a hair of scale and a soft downward shadow, never color. Reverses cleanly and falls back to a plain legible list under reduced motion.

motionfree

Installation

CLI

npx shadcn@latest add @crucible/shingle

Manual — install dependencies, then copy the source

npm install motion

Props

PropTypeDefaultDescription
childrenReact.ReactNodeThe cards. Each top-level child becomes one full-width card that pins and stacks. Falls back to a neutral demo deck when omitted.
scrollContainerReact.RefObject<HTMLElement | null>Scroll container to track instead of the window. Pass a ref to an internal overflow-y-auto element (this component's own demo does) so the pin position is measured against the scrollport, not the page.
itemDistancenumber44Vertical gap in px between cards in normal flow — also the scroll distance that advances the deck by one card.
stackPositionnumber | string"18%"Pin anchor: how far below the top of the scrollport a card seats. Number = px, string = any CSS length/percent (e.g. "18%").
itemScalenumber0.05Scale removed per card of depth as the deck recedes.
itemStackDistancenumber12Upward peek in px per card of depth, so receding cards show above the front.
maxDepthnumber4How many cards deep the recession clamps (the deepest visible layer).
blurAmountnumber1.5Blur radius in px added per card of depth.
rotationAmountnumber0Rotation in degrees per card of depth (alternating sign for an overlapped-tile feel).
settlenumber2The signature detent: px of overshoot as a card seats at the pin point (tiny settle-and-lock). 0 disables it.
onStackComplete() => voidFired once when the final card seats at the pin.
Also accepts all props of Omit<React.ComponentPropsWithoutRef<"div">, "children">they pass through to the underlying element.

Honors prefers-reduced-motion with a designed static fallback, and passes className through.