Skip to content

Carousel

An accessible, draggable carousel powered by data-slot, with horizontal and vertical layouts.

1
2
3
4
5

---
import { Carousel, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<Carousel aria-label="Horizontal single cards" class="mx-auto max-w-86.5">
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">1</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">2</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">3</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">4</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">5</div></CarouselSlide>
</Carousel>
Terminal window
bunx bejamas add carousel
---
import { Carousel, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<Carousel aria-label="Featured cards" class="max-w-sm">
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 1</div></CarouselSlide>
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 2</div></CarouselSlide>
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 3</div></CarouselSlide>
</Carousel>
Prop Type Default
orientation "horizontal" | "vertical" axis === "y" ? "vertical" : "horizontal"
variant "single" | "multiple" "single"
defaultIndex number 0
“data-loop” boolean | string false
drag boolean true
controls boolean true
showCounter boolean orientation === "horizontal" && variant === "single"
counterLabel string variant === "multiple" ? "Page" : "Card"
previousLabel string "Previous slide"
nextLabel string "Next slide"
contentClass string
axis "x" | "y" "x"
snapType "mandatory" | "proximity" "mandatory"
fade boolean false
class string
1
2
3
4
5

---
import { Carousel, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<Carousel aria-label="Horizontal single cards" class="mx-auto max-w-86.5">
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">1</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">2</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">3</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">4</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">5</div></CarouselSlide>
</Carousel>
1
2
3
4
5
6
---
import { Carousel, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<Carousel aria-label="Horizontal multiple cards" variant="multiple" class="mx-auto max-w-108">
<CarouselSlide>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">1</div>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">2</div>
</CarouselSlide>
<CarouselSlide>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">3</div>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">4</div>
</CarouselSlide>
<CarouselSlide>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">5</div>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">6</div>
</CarouselSlide>
</Carousel>
1
2
3
4
5
---
import { Carousel, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<Carousel aria-label="Vertical single cards" orientation="vertical" class="mx-auto max-w-75" contentClass="h-50">
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold h-full">1</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold h-full">2</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold h-full">3</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold h-full">4</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold h-full">5</div></CarouselSlide>
</Carousel>
1
2
3
4
5
6
---
import { Carousel, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<Carousel aria-label="Vertical multiple cards" orientation="vertical" variant="multiple" class="mx-auto max-w-75" contentClass="h-79">
<CarouselSlide>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold min-h-0">1</div>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold min-h-0">2</div>
</CarouselSlide>
<CarouselSlide>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold min-h-0">3</div>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold min-h-0">4</div>
</CarouselSlide>
<CarouselSlide>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold min-h-0">5</div>
<div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold min-h-0">6</div>
</CarouselSlide>
</Carousel>

Use CarouselRoot to compose your own layout. Place CarouselPrevious and CarouselNext anywhere inside the root, including a header above CarouselContent. The runtime keeps their disabled states in sync. Place slides directly inside CarouselContent.

CarouselRoot accepts orientation, axis, variant, defaultIndex, data-loop, and drag. CarouselContent inherits orientation and drag from the root and accepts snapType, fade, and class; give vertical content a height. Controls accept button attributes, variant, size, and class, with an optional slot for a custom icon or text and aria-label for a custom accessible name. The composed root renders only its slot; the existing Carousel continues to provide its default controls, viewport, and counter.

Featured cards

Card 1
Card 2
Card 3
---
import { CarouselContent, CarouselNext, CarouselPrevious, CarouselRoot, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<CarouselRoot aria-label="Featured cards" class="mx-auto max-w-sm space-y-4">
<header class="flex items-center justify-between gap-4">
<h3 class="font-semibold">Featured cards</h3>
<div class="flex gap-2">
<CarouselPrevious />
<CarouselNext />
</div>
</header>
<CarouselContent>
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 1</div></CarouselSlide>
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 2</div></CarouselSlide>
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 3</div></CarouselSlide>
</CarouselContent>
</CarouselRoot>

Set data-loop="true" to wrap button, keyboard, and programmatic navigation from the last slide to the first and back. Looping defaults to false.

1
2
3

---
import { Carousel, CarouselSlide } from '@bejamas/registry/ui/carousel';
---
<Carousel aria-label="Looping cards" data-loop="true" class="mx-auto max-w-86.5">
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">1</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">2</div></CarouselSlide>
<CarouselSlide><div class="flex items-center justify-center rounded-lg bg-muted text-muted-foreground font-semibold aspect-4/5">3</div></CarouselSlide>
</Carousel>

The carousel emits custom events on its root element:

Event Detail Description
carousel:change { index: number } Fired when the active slide changes. The index is zero-based.
<Carousel id="my-carousel" aria-label="Featured cards" class="max-w-sm">
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 1</div></CarouselSlide>
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 2</div></CarouselSlide>
<CarouselSlide><div class="rounded-lg bg-muted p-6">Card 3</div></CarouselSlide>
</Carousel>
const carousel = document.getElementById('my-carousel');
carousel.addEventListener('carousel:change', (event) => {
console.log('Active slide:', event.detail.index);
});

Dispatch carousel:set on the root to select a slide or navigate:

const carousel = document.getElementById('my-carousel');
// Select the third slide (zero-based index).
carousel.dispatchEvent(new CustomEvent('carousel:set', {
detail: { index: 2 }
}));
// Move to the next slide.
carousel.dispatchEvent(new CustomEvent('carousel:set', {
detail: { action: 'next' }
}));
// Move to the previous slide.
carousel.dispatchEvent(new CustomEvent('carousel:set', {
detail: { action: 'prev' }
}));

The carousel exposes these attributes for styling and state queries:

Attribute Element Description
data-orientation carousel Current orientation (horizontal or vertical)
data-variant carousel Layout variant (single or multiple)
data-loop carousel Set to "true" to wrap navigation at either end; defaults to "false"
data-index carousel Zero-based index of the active slide
data-dragging carousel Present during an active drag gesture
data-state carousel-item Current slide state (active or inactive)