Orbit Card Stack
InteractiveA draggable card deck (testimonials, photos, prompts): the top card can be flung left or right to spring it off-screen and orbit to the back, while prev/next buttons and the arrow keys advance and rewind through a depth-stacked pile. Exposes real carousel semantics with a polite live region and one exposed slide at a time; under prefers-reduced-motion it reorders instantly with no drag or throw physics.
Inspired by Componentry — Orbit Card Stack (free, MIT) — independently built, not affiliated. Attribution policy
Flick the top card and it arcs off-screen with a proper spring throw, then orbits to the back.
Installation
npx shadcn@latest add https://parable-three.vercel.app/r/orbit-card-stack.jsonRuns the shadcn CLI — the component source is copied intocomponents/parable/orbit-card-stack.tsx. You own it from there.
Usage
The exact code rendering the preview above.
<div style={{ width: "100%", maxWidth: 320, margin: "0 auto" }}>
<OrbitCardStack
accent="#8b5cf6"
cards={[
{ q: "Flick the top card and it arcs off-screen with a proper spring throw, then orbits to the back.", a: "Ada Lovelace", r: "Head of Design", c: "#8b5cf6" },
{ q: "Drag, buttons, and arrow keys all land on the same clean motion. Shipped it in an afternoon.", a: "Grace Hopper", r: "VP Engineering", c: "#ec4899" },
{ q: "The reduced-motion fallback stays fully legible. Rare to see it done right.", a: "Alan Turing", r: "Principal", c: "#22d3ee" },
].map((t, i) => (
<div key={i} className="flex h-full flex-col justify-between">
<div className="flex gap-1" style={{ color: t.c }} aria-hidden>
{[0, 1, 2, 3, 4].map((s) => (
<Star key={s} size={13} fill="currentColor" strokeWidth={0} />
))}
</div>
<p className="text-[15px] leading-relaxed text-white/90">{t.q}</p>
<div>
<div className="text-sm font-semibold text-white">{t.a}</div>
<div className="text-xs text-white/50">{t.r}</div>
</div>
</div>
))}
/>
</div>Props
OrbitCardStackProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "onChange" | "children">
| Prop | Type | Default | Description |
|---|---|---|---|
| cards* | React.ReactNode[] | — | Cards to stack, front-most first. Each is placed inside the deck surface. |
| onChange | (index: number) => void | — | Fires with the new front-card index whenever the deck advances or rewinds. |
| loop | boolean | true | Wrap at the ends — dismissing the last card returns to the first. Default true. |
| accent | string | "#8b5cf6" | Accent for the swipe hint, focus rings, and the active control. Defaults to Parable violet `#8b5cf6` (mirrors the site's `--pb-violet` token). |
| label | string | "Card stack" | Accessible name for the carousel region. |
npm dependencies
- motion
- lucide-react