Cylinder carousel
FreeItems arranged around a rotating cylinder.
pnpm dlx shadcn@latest add https://caliperui.site/r/cylinder-carousel.jsonLaunch page
Marketing
Docs home
Docs
Pricing
Marketing
Changelog
Docs
Dashboard
App
Settings
App
Sign in
Account
Empty state
App
Dashboard selected, 5 of 8
When to use #
Use it for
- A showpiece for a small set of visuals.
- Playful landing pages.
Skip it when
- The items carry detailed text.
- Accessibility of every item must be quick to reach; prefer a list.
Installation #
terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/cylinder-carousel.json
Adds its 2 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.
Usage #
This is the code behind the preview above.
example.tsx
import { useState } from "react" import { CylinderCarousel } from "@/components/ui/cylinder-carousel" const templates = [ { id: "launch", label: "Launch page", kind: "Marketing", tone: "var(--chart-1)" }, { id: "docs", label: "Docs home", kind: "Docs", tone: "var(--chart-3)" }, { id: "pricing", label: "Pricing", kind: "Marketing", tone: "var(--chart-5)" }, { id: "changelog", label: "Changelog", kind: "Docs", tone: "var(--chart-4)" }, { id: "dash", label: "Dashboard", kind: "App", tone: "var(--chart-2)" }, { id: "settings", label: "Settings", kind: "App", tone: "var(--chart-6)" }, { id: "signin", label: "Sign in", kind: "Account", tone: "var(--chart-3)" }, { id: "empty", label: "Empty state", kind: "App", tone: "var(--chart-1)" }, ].map((t) => ({ ...t, content: ( <div className="grid h-full grid-rows-[1fr_auto]"> <div className="p-3" style={{ background: `color-mix(in srgb, ${t.tone} 30%, var(--surface))` }}> <div className="h-2 w-2/3 rounded-full" style={{ background: `color-mix(in srgb, ${t.tone} 70%, var(--foreground))` }} /> <div className="mt-2 h-2 w-1/2 rounded-full bg-foreground/15" /> <div className="mt-6 grid grid-cols-2 gap-2"> <div className="h-10 rounded-control bg-foreground/10" /> <div className="h-10 rounded-control bg-foreground/10" /> </div> </div> <div className="border-t border-border px-3 py-2"> <p className="text-sm font-medium">{t.label}</p> <p className="text-xs text-muted-foreground">{t.kind}</p> </div> </div> ), })); export function CylinderCarouselDemo() { const [template, setTemplate] = useState("dash"); return ( <CylinderCarousel label="Page templates" items={templates} value={template} onValueChange={setTemplate} /> ); }
API reference #
CylinderCarousel
component
PropTypeDefaultDescription
label *string—
items *readonly CylinderItem[]—
valuestring—
defaultValuestring—
onValueChange((id: string) => void)—
itemWidthnumber150Card size in pixels; the ring's radius follows from it and the card count.
itemHeightnumber200
classNamestring—
Accessibility #
It exposes listbox, option and status semantics, with aria-label, aria-orientation and aria-selected kept in step with state. Changes that matter are announced through a live region.
KeyBehaviour
→Moves to the next item or value.
↓Moves to the next item or value.
←Moves to the previous item or value.
↑Moves to the previous item or value.
HomeJumps to the first item or the minimum.
EndJumps to the last item or the maximum.
Motion #
It checks prefers-reduced-motion itself and settles immediately instead of travelling. Every state is still reachable.