Perspective Tunnel Carousel
A row of 3:4 cards riding a corridor in real CSS 3D: tiny and flat on the back wall at the vanishing point, growing huge and angled along the side walls as they leave past the edges. The row drifts on a transform-only rAF clock, pauses on hover, focus and off-screen, scrubs with drag and throws with inertia, centres a card on keyboard focus and becomes a scroll-snap row under reduced motion.
Try it: Hover a card to pause it, drag to scrub, or Tab through the cards — each one glides to the centre.
Brings its own background. The demo frame paints an off-white (light) / ink (dark) stage with film grain; the component itself is transparent, so place it on any surface.
Installation
Install with the shadcn CLI:
npx shadcn@latest add https://framekit-ui.vercel.app/r/perspective-tunnel-carousel.jsonOr, with the @framekit namespace registered: npx shadcn@latest add @framekit/perspective-tunnel-carousel. You can also copy the source into components/ui/perspective-tunnel-carousel.tsx (view on GitHub).
Usage
import { PerspectiveTunnelCarousel } from '@/components/ui/perspective-tunnel-carousel'Props
| Prop | Type | Default | Notes |
|---|---|---|---|
items | TunnelItem[] | DEFAULT_TUNNEL_ITEMS | { id, label?, href?, tint?, tone?, content?, src?, alt? } — content can be any node, e.g. a live component. |
children | ReactNode | — | Alternative to items: each child becomes one card. |
speed / direction | number / 'left' | 'right' | 0.32 / 'left' | Drift in cards per second and its direction. |
centerWidth / edgeWidth | number | auto | Screen width of a flat back-wall card, and the real card width where the walls meet the edge (px). |
flatCount / wallAngle / perspective | number | auto / 90 / auto | Cards flat on the back wall, the angle the side walls turn to (90 = straight corridor) and the CSS perspective. |
gap | number | 0.08 | Gap between cards as a share of the card width. |
paused / defaultPaused / onPausedChange | boolean / boolean / (p) => void | — / false / — | Controlled or uncontrolled pause. |
pauseOnHover / draggable / controls | boolean | true | Hover pause, drag-to-scrub and the built-in pause button. |
clip | boolean | true | Clip to the root box; turn off to let cards bleed into a larger clipped section (hero use). |
lazy | boolean | true | Mount card content only once it first drifts into view. |
linkAs | ElementType | 'a' | Element for linked cards (e.g. a router link that accepts href). |
label / className | string | — | List name for assistive tech; root classes (give it a height). |
Dependencies
lucide-react
Related Motion Showcase components
- Scroll Product Reveal
An Apple-style pinned product story: scroll scrubs a CSS 3D device through rotation, screen glow and an exploded-layer view while chapter copy cross-fades with a blur. Scrolls inside its own…
- Blur Cascade Heading
A keynote-style display headline that resolves out of focus: words or letters rise through a mask from blur to sharp on springs, and highlight words land last in a gradient.
- App Store Expand Card
Story cards that grow into a full detail sheet with a shared-layout spring: art, headline and surface morph from the card, the body rises in behind. Esc or close collapses it and returns focus.
- Spring Reorder List
A priority list reordered by dragging the grip: the lifted row scales and deepens its shadow while siblings part on springs. Keyboard ↑ ↓ on a grip moves rows with live announcements.
- Liquid Glass Tab Bar
An iOS 26-style floating glass tab bar: a refractive lens slides between tabs and stretches with distance like liquid, icons pop on select, plus a floating accessory button.
- Timeline Scrubber
A media scrubber with a waveform that lights up behind the playhead, a thumb that grows while dragging, chapter markers that snap within 1.5 s, a time bubble and full slider keyboard support.