crucıble

Pantograph

Morphing flyout nav where one shared dark-glass panel slides and resizes between triggers instead of closing and reopening — an anticipation-stretch morph with a caret that tracks the active trigger and content that crossfades in from the direction of travel. Hover-intent grace, full disclosure semantics (focus opens, Escape closes, arrow keys move triggers), and instant repositioning under reduced motion.

motionfree

Installation

CLI

npx shadcn@latest add @crucible/pantograph

Manual — install dependencies, then copy the source

npm install motion

Props

PropTypeDefaultDescription
itemsPantographItem[]a small generic setTriggers to render, left to right.
openOn"hover" | "click""hover"How a flyout opens on pointer devices.
caretbooleantrueShow the caret that tracks the active trigger.
closeDelaynumber150Grace period in ms before the panel closes after the pointer leaves — the hover-intent forgiveness that stops flicker on diagonal travel.
contentTransition"directional" | "crossfade""directional"Incoming content slides in from the direction of travel ("directional") or simply crossfades in place ("crossfade").
spring{ stiffness?: number; damping?: number; mass?: number }Spring physics for the panel morph.
minWidthnumber200Min panel width in px.
maxWidthnumber560Max panel width in px.
valuestring | nullControlled active trigger value; null means closed.
defaultValuestring | nullnull (closed)Initial active trigger when uncontrolled.
onValueChange(value: string | null) => voidFires whenever the active trigger changes (a value, or null on close).
Also accepts all props of Omit< React.ComponentPropsWithoutRef<"nav">, "onChange" | "value" | "defaultValue" >they pass through to the underlying element.

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