Early bird: 30% off Dashral ProSee pricing

Dashral

Depth CarouselPro

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.

example.tsx
"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.

PropTypeDefault
itemsT[]

Required.

—
renderItem(item: T, state: { active: boolean; index: number }) => React.ReactNode

Required. An item's face: it fills the item, which the carousel shapes (radius, shadow) and moves.

—
getLabel(item: T) => string

Required. What an item is called, for screen readers.

—
getKey(item: T, index: number) => string

An item's key; its index by default.

(_item, i) => String(i)
indexnumber

The item in front.

—
defaultIndexnumber0
onIndexChange(index: number) => void—
onSelect(item: T, index: number) => void

The item in front, clicked (or Enter on it): open it, play it…

—
itemWidthnumber

An item's width in px; its height follows aspect.

240
aspectnumber

An item's width over its height; 3 / 4 (portrait) by default.

3 / 4
dotsboolean

The dots under it, one per item.

true
loopboolean

Goes 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
navboolean

Previous and next buttons at the gallery's sides. Off by default: it's dragged, clicked and keyed.

false
labelstring

The gallery's name, for screen readers.

"Gallery"
classNamestring—
classNamesDepthCarouselClassNames—

type DepthCarouselClassNames

PropTypeDefault
rootstring—
stagestring—
itemstring—
dotsstring—

Keyboard

KeysAction
←or→The previous or next item
HomeorEndThe first or last item
EnterorSpaceSelect 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.