A 3D gallery you can throw: the item in front faces you, the rest turn away into depth, and a throw glides to a stop on an item.
Installation
When to use
- A shelf of things people browse by picture: collections, albums, products, templates, featured stories.
- A hero or showcase where the gallery itself should feel like something to play with.
When not to use
- Items people compare or read side by side: a grid shows more at once.
- Slides that carry the page's message one after another: the Carousel keeps them flat and in order.
Usage
Depth Carousel with your own data: a whole component you can paste as a page.
"use client"
import { DepthCarousel } from "@/components/dashral/pro/depth-carousel"
import { useState } from "react"
const albums = [
{ id: "a1", title: "Dawn", color: "bg-orange-700" },
{ id: "a2", title: "Tide", color: "bg-sky-800" },
{ id: "a3", title: "Moss", color: "bg-emerald-800" },
{ id: "a4", title: "Dusk", color: "bg-fuchsia-900" },
]
export default function Albums() {
const [index, setIndex] = useState(1)
return (
<DepthCarousel
label="Albums"
items={albums}
index={index}
onIndexChange={setIndex}
getKey={(album) => album.id}
getLabel={(album) => album.title}
// Clicking the item in front: open it.
onSelect={(album) => console.log("open", album.id)}
renderItem={(album) => (
<div className={`flex size-full items-end p-4 text-lg font-semibold text-white ${album.color}`}>{album.title}</div>
)}
/>
)
}API reference
DepthCarousel
A 3D gallery you can throw: the item in front faces you and the others turn away into depth. Drag and let go to glide through (it stops on an item), click one beside to bring it forward, or use the arrow keys. Generic: renderItem draws each face.
| Prop | Type | Default |
|---|---|---|
items | T[]Required. | — |
renderItem | (item: T, state: { active: boolean; index: number }) => React.ReactNodeRequired. An item's face: it fills the item, which the carousel shapes (radius, shadow) and moves. | — |
getLabel | (item: T) => stringRequired. What an item is called, for screen readers. | — |
getKey | (item: T, index: number) => stringAn item's key; its index by default. | (_item, i) => String(i) |
index | numberThe item in front. | — |
defaultIndex | number | 0 |
onIndexChange | (index: number) => void | — |
onSelect | (item: T, index: number) => voidThe item in front, clicked (or Enter on it): open it, play it… | — |
itemWidth | numberAn item's width in px; its height follows | 240 |
aspect | numberAn item's width over its height; 3 / 4 (portrait) by default. | 3 / 4 |
dots | booleanThe dots under it, one per item. | true |
loop | booleanGoes round without an end: after the last item comes the first. Needs 5 items or more (fewer would show the same item on both sides), so with fewer it stays a row with ends. | false |
nav | booleanPrevious and next buttons at the gallery's sides. Off by default: it's dragged, clicked and keyed. | false |
label | stringThe gallery's name, for screen readers. | "Gallery" |
className | string | — |
classNames | DepthCarouselClassNames | — |
type DepthCarouselClassNames
| Prop | Type | Default |
|---|---|---|
root | string | — |
stage | string | — |
item | string | — |
dots | string | — |
Keyboard
| Keys | Action |
|---|---|
| ←or→ | The previous or next item |
| HomeorEnd | The first or last item |
| EnterorSpace | Select the item in front (onSelect) |
Accessibility
- A region with the role description "carousel", named by label; each item is a group with the role description "slide", the one in front marked aria-current.
- A live region says the item in front and its place ("Lisbon, 2 of 7").
- The dots are buttons named "Show <item>"; the side buttons (nav) are named Previous and Next.
Motion
- A throw glides on with its speed and comes to rest on an item; past either end it gets heavier.
- Items beside the front one turn away, step back and fade with distance, each on a soft shadow.
- Smooth tweens on [0.4, 0, 0.2, 1], never springs. With reduced motion there's no turn or depth: the items slide flat.