---
title: Carousel
description: A carousel with motion and swipe built using Embla.
icon: gallery-horizontal
author:
  name: shadcn
  url: https://github.com/shadcn
search:
  keywords: [slider, gallery]
mode: wide
---

<PoweredBy
  packages={[
    { name: 'Shadcn UI', url: 'https://ui.shadcn.com/docs/components/carousel' },
    { name: 'Embla Carousel', url: 'https://www.embla-carousel.com/' },
    { name: 'Lucide', url: 'https://lucide.dev/' },
  ]}
/>

### Installation

```package-install
npx shadcn@latest add @redpanda/carousel
```

<ComponentPreview name="carousel-demo" />

## Usage

Carousel displays a scrollable, swipeable collection of slides with previous/next controls and keyboard arrow navigation.

```tsx
import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from '@/components/redpanda-ui/carousel';
```

```tsx
<Carousel>
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>
```

## Anatomy

```
Carousel Structure:
┌─────────────────────────────────────────────────────┐
│ <Carousel> (region, keyboard navigation)            │
│ ┌─────────────────────────────────────────────────┐ │
│ │ <CarouselContent> (scroll viewport)             │ │
│ │ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐ │ │
│ │ │<CarouselItem>│ │<CarouselItem>│ │<CarouselItem>│ │ │
│ │ │  (slide)    │ │  (slide)    │ │  (slide)    │ │ │
│ │ └─────────────┘ └─────────────┘ └─────────────┘ │ │
│ └─────────────────────────────────────────────────┘ │
│  <CarouselPrevious />            <CarouselNext />    │
└─────────────────────────────────────────────────────┘
```

## Slot Guidelines

### CarouselContent

**Purpose:** The scroll viewport that holds all slides.

**Best practices:**

- Set `gap="sm"` or `gap="md"` on `Carousel` to coordinate track and item spacing. The default is `md` (16px); `sm` is 4px.
- For vertical carousels, set an explicit viewport height (for example, `h-50`).

```tsx
<Carousel gap="sm">
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
</Carousel>
```

### CarouselItem

**Purpose:** A single slide. Has `role="group"` and `aria-roledescription="slide"`.

**Best practices:**

- Use `basis-*` utilities to control how many slides are visible at once
- Keep slide content self-contained; avoid nested pressables inside clickable slides

```tsx
{
  /* 33% of the carousel width */
}
<CarouselItem className="basis-1/3">...</CarouselItem>;
```

### CarouselPrevious / CarouselNext

**Purpose:** Navigation buttons. They render the registry [Button](/docs/button) and accept all of its props (`variant`, `size`, `testId`, ...). They disable automatically when the carousel cannot scroll further.

```tsx
<CarouselPrevious variant="ghost" />
<CarouselNext variant="ghost" />
```

## Props

### Carousel

| Prop          | Type                         | Default        | Description                                                               |
| ------------- | ---------------------------- | -------------- | ------------------------------------------------------------------------- |
| `opts`        | `CarouselOptions`            | -              | [Embla options](https://www.embla-carousel.com/api/options/)              |
| `plugins`     | `CarouselPlugin`             | -              | [Embla plugins](https://www.embla-carousel.com/plugins/) such as Autoplay |
| `gap`         | `'sm' \| 'md'`               | `'md'`         | Coordinated slide spacing: 4px or 16px                                    |
| `orientation` | `'horizontal' \| 'vertical'` | `'horizontal'` | Scroll axis                                                               |
| `setApi`      | `(api: CarouselApi) => void` | -              | Receives the Embla API instance for imperative control                    |
| `testId`      | `string`                     | -              | Sets `data-testid` for testing                                            |

### Options

Options are passed to Embla via the `opts` prop:

```tsx
<Carousel
  opts={{
    align: 'start',
    loop: true,
  }}
>
  ...
</Carousel>
```

### API

Use the `setApi` prop with `CarouselApi` state to listen to events and control the carousel imperatively:

```tsx
import { Carousel, type CarouselApi } from '@/components/redpanda-ui/carousel';

function Example() {
  const [api, setApi] = useState<CarouselApi>();

  useEffect(() => {
    if (!api) {
      return;
    }

    api.on('select', () => {
      // Do something on select
    });
  }, [api]);

  return <Carousel setApi={setApi}>...</Carousel>;
}
```

### Plugins

Plugins such as [Autoplay](https://www.embla-carousel.com/plugins/autoplay/) are passed via the `plugins` prop:

```tsx
import Autoplay from 'embla-carousel-autoplay';

<Carousel plugins={[Autoplay({ delay: 2000 })]}>...</Carousel>;
```

## Examples

### Basic

<ComponentPreview name="carousel-demo" description="Default horizontal carousel with previous and next controls." />

### Sizes

Use the `basis-*` utility on `<CarouselItem />` to size slides.

<ComponentPreview name="carousel-size-demo" description="Multiple slides per view using basis utilities." />

### Vertical

Set `orientation="vertical"` and give `<CarouselContent />` an explicit height.

<ComponentPreview name="carousel-vertical-demo" description="Vertical carousel with rotated navigation buttons." />

### With API

<ComponentPreview
  name="carousel-api-demo"
  description="Track the selected slide with setApi and the CarouselApi events."
/>

## Accessibility

- The carousel root has `role="region"` and `aria-roledescription="carousel"`
- Each slide has `role="group"` and `aria-roledescription="slide"`
- Left/Right arrow keys scroll to the previous/next slide when focus is inside the carousel
- Navigation buttons include screen-reader-only labels and disable when scrolling is not possible

## Credits

- We take our inspiration from [Shadcn UI](https://ui.shadcn.com/docs/components/carousel) for the carousel component and style.
- Built on top of [Embla Carousel](https://www.embla-carousel.com/).
