Carousel
A carousel with motion and swipe built using Embla.
Made by shadcnPowered by
Installation
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-4is the default) together with matchingpl-4on 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
Carousel
| Prop | Type | Default | Description |
|---|---|---|---|
opts | CarouselOptions | - | Embla options |
plugins | CarouselPlugin | - | Embla plugins such as Autoplay |
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:
<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
Sizes
Use the basis-* utility on <CarouselItem /> to size slides.
Vertical
Set orientation="vertical" and give <CarouselContent /> an explicit height.
With API
Accessibility
- The carousel root has
role="region"andaria-roledescription="carousel" - Each slide has
role="group"andaria-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 for the carousel component and style.
- Built on top of Embla Carousel.
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