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.json

Or, 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

Component props
PropTypeDefaultNotes
itemsTunnelItem[]DEFAULT_TUNNEL_ITEMS{ id, label?, href?, tint?, tone?, content?, src?, alt? } — content can be any node, e.g. a live component.
childrenReactNode—Alternative to items: each child becomes one card.
speed / directionnumber / 'left' | 'right'0.32 / 'left'Drift in cards per second and its direction.
centerWidth / edgeWidthnumberautoScreen width of a flat back-wall card, and the real card width where the walls meet the edge (px).
flatCount / wallAngle / perspectivenumberauto / 90 / autoCards flat on the back wall, the angle the side walls turn to (90 = straight corridor) and the CSS perspective.
gapnumber0.08Gap between cards as a share of the card width.
paused / defaultPaused / onPausedChangeboolean / boolean / (p) => void— / false / —Controlled or uncontrolled pause.
pauseOnHover / draggable / controlsbooleantrueHover pause, drag-to-scrub and the built-in pause button.
clipbooleantrueClip to the root box; turn off to let cards bleed into a larger clipped section (hero use).
lazybooleantrueMount card content only once it first drifts into view.
linkAsElementType'a'Element for linked cards (e.g. a router link that accepts href).
label / classNamestring—List name for assistive tech; root classes (give it a height).

Dependencies

  • lucide-react
  • 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.

See all 15 Motion Showcase components