Skip to content
WANCRAFT

H.613D & Experimental

Ring Carousel

A 3D ring of cards you can drag, flick and snap, with keyboard control and a flat reduced-motion fallback.

NewReactMotionCSSTailwindadvanced

Overview

Ring Carousel arranges cards around a cylinder in CSS 3D: each card is rotated by 360°/n and pushed out by a radius computed from the card width, gap and count, then the ring is tilted and spun as a whole. Drag to spin — the flick velocity carries into a spring that snaps to the nearest card — or use the previous/next buttons, the arrow keys while the stage is focused, or Home/End. Auto-rotation steps one card at a time and pauses on hover, keyboard focus, offscreen and in hidden tabs, with a stop/start button. Only the front card is interactive; the others are inert and hidden from assistive tech, and the front card is announced as "Item 3 of 8: title". Narrow containers scale the scene to fit. With reduced motion it becomes a flat, snap-scrolling row.

01Installation

Method 01 · Registry

CLI verified

Uses the shadcn CLI in a project initialised with shadcn init. Installs the file to components/ui/ring-carousel.tsx and adds its dependencies.

npx shadcn@latest add https://wancraft.my/r/ring-carousel.json

Method 02 · Copy and paste

  1. 1. Install the dependencies below.
  2. 2. Copy the source from the Code tab into components/ui/ring-carousel.tsx.
  3. 3. Import it and adjust props.
npm install motion
Dependencies
motion
Peer
react ≥ 18, tailwindcss ≥ 4
Added
13 Oct 2026
Updated
—

02Usage

The default configuration. Change properties above to generate a tailored example in the Usage tab.

example.tsx
import { RingCarousel, type RingCarouselItem } from "@/components/ui/ring-carousel";

export function RingCarouselExample() {
  const items: RingCarouselItem[] = ["Precision", "Restraint", "Intent", "Clarity", "Rhythm", "Craft"].map((title, i) => ({
    id: title.toLowerCase(),
    label: title,
    content: (
      <div className="flex h-full flex-col justify-between p-5">
        <span className="font-mono text-xs text-zinc-500">0{i + 1}</span>
        <p className="text-xl font-semibold tracking-tight">{title}</p>
      </div>
    ),
  }));

  return (
    <RingCarousel items={items} label="Principles" />
  );
}

03Props

PropTypeDefaultDescription
items{ id: string; content: ReactNode; label: string }[]—Cards around the ring; label names the slide and is announced.
labelstring"Featured"Accessible name of the carousel.
cardWidthnumber220Card width in px; the radius is (cardWidth + gap) / 2 / tan(π / n).
cardHeightnumber1.3 × cardWidthCard height in px.
gapnumber28Space between neighbouring cards around the ring, in px.
perspectivenumber1200CSS perspective in px.
tiltnumber-8Ring tilt in degrees (negative looks down onto it).
autoRotatebooleantrueStep one card per interval; pauses on hover, keyboard focus, offscreen and hidden tabs.
intervalnumber3200Auto-rotate interval in ms (minimum 1200).
showControlsbooleantruePrevious, stop/start and next buttons plus the counter.
accentstring"#FF7448"Front-card ring and focus colour.
defaultIndex / onIndexChangenumber / (index) => void0Initial front card; called when the front card changes.

04Features

  • 01Cylinder layout with the radius derived from card width, gap and count
  • 02Drag to spin with velocity-carrying spring snap; a drag never clicks the card under it
  • 03Buttons, arrow keys and Home/End; clicking a side card brings it to the front
  • 04Auto-rotation that pauses on hover, keyboard focus, offscreen and hidden tabs, with a stop/start button
  • 05Depth shading and plain back faces; the scene scales down to fit narrow containers
motion · springmotion · continuousinput · draginput · pointerinput · clickinput · keyboardinput · focusinput · viewport

05Accessibility

  • A <section> with aria-roledescription="carousel"; each card is a group with aria-roledescription="slide" named "3 of 8: title".
  • Only the front card is interactive: the others are inert and aria-hidden.
  • The focusable stage rotates with Left/Right arrows (Home/End jump); buttons are labelled and have visible focus rings.
  • A polite live region announces "Item 3 of 8: title"; it is silenced while rotating automatically.
  • Auto-rotation can be stopped with a button and pauses on keyboard focus (WCAG 2.2.2).
  • prefers-reduced-motion replaces the ring with a flat, snap-scrolling list where every card is reachable.

06Limitations

  • Card contents render inside a 3D-transformed layer; very large cards or heavy content can soften text on some GPUs.