Redpanda UIRedpanda UI

Carousel

A carousel with motion and swipe built using Embla.

Made by shadcn

Installation

Loading component...

Usage

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

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/redpanda-ui/carousel"
<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:

  • Apply negative margin utilities (-ml-4 is the default) together with matching pl-4 on items to control spacing
  • For vertical carousels, set an explicit height (e.g. h-[200px])
<CarouselContent className="-ml-2">
  <CarouselItem className="pl-2">...</CarouselItem>
</CarouselContent>

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
{/* 33% of the carousel width */}
<CarouselItem className="basis-1/3">...</CarouselItem>

CarouselPrevious / CarouselNext

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

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

Props

PropTypeDefaultDescription
optsCarouselOptions-Embla options
pluginsCarouselPlugin-Embla plugins such as Autoplay
orientation'horizontal' | 'vertical''horizontal'Scroll axis
setApi(api: CarouselApi) => void-Receives the Embla API instance for imperative control
testIdstring-Sets data-testid for testing

Options

Options are passed to Embla via the opts prop:

<Carousel
  opts={{
    align: "start",
    loop: true,
  }}
>
  ...
</Carousel>

API

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

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 are passed via the plugins prop:

import Autoplay from "embla-carousel-autoplay"

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

Examples

Basic

Loading component...

Sizes

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

Loading component...

Vertical

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

Loading component...

With API

Loading component...

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

Recent changes

  • patchv1.2.0Pin shipped dependency floors to the version we develop against. Registry items now declare ranges like `^5.1.9` (the actual installed version) instead of collapsing to `^5.0.0`, so consumers start on the known-tested baseline while caret semantics still allow any compatible release within the same major.#133
  • minorv1.1.0Theme docs refresh, readability pass on semantic foregrounds, and consumer-facing Base UI regression fixes.#121
  • minorv1.0.0Post-Base-UI polish. Public API unchanged.#116
  • majorv1.0.0Migrate every Radix-based primitive to `@base-ui/react@^1.4.0` (Base UI).#114
  • minorv0.3.0Add theme-provider component to the registry with documentation and tests. Includes playground type improvements (export RegistryItem, remove as-const boilerplate) and docs site dark mode border color fix.#109
See full history →
Built by malinskibeniamin. The source code is available on GitHub.

On this page