Carousel
A carousel with motion and swipe for slides, built on Embla Carousel.
Installation
The component is exported from @by/experience-system. Add the package with your package manager:
pnpm add @by/experience-systemIn this monorepo, depend on the workspace package (for example via workspace:* or your catalog) so imports resolve to packages/experience-system.
Composition
Use the following composition to build a Carousel:
Carousel
├── CarouselContent
│ └── CarouselItem (repeat per slide)
├── CarouselPrevious
└── CarouselNextCarouselContent owns the Embla viewport ref; each slide is a CarouselItem. Place CarouselPrevious and CarouselNext as siblings of CarouselContent (not inside the scrollable track). ThemeProvider passes Embla direction for RTL-aware arrows and keyboard navigation.
Usage
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@by/experience-system';The carousel primitives are client components ('use client'). Use them inside client boundaries or other client components.
<Carousel className="w-full max-w-xs">
<CarouselContent>
{Array.from({ length: 3 }).map((_, index) => (
<CarouselItem key={index}>
<div className="flex items-center justify-center p-6 text-2xl font-semibold">{index + 1}</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Examples
The live preview at the top of this page uses carousel-usage (slides in Card shells with prev/next).
Sizes
Set slide width with basis-* on CarouselItem (for example basis-1/3 for three visible slides at a breakpoint).
Spacing
Use negative margin on CarouselContent and matching padding on each CarouselItem for gap between slides (RTL-friendly, for example -ms-*! / ps-*! horizontally).
Vertical
Set orientation="vertical" on Carousel. For spacing, use -mt-*! on content and pt-*! on items.
With API
Pass setApi to receive the Embla API when ready—use it to read the selected index, subscribe to select, or call scroll methods.
API Reference
Subsection titles name the exports from @by/experience-system. Embla behavior follows the Embla Carousel API.
Carousel
Root region wrapping the carousel context.
| Prop | Type | Default |
|---|---|---|
orientation | horizontal | vertical | horizontal |
opts | Embla options | — |
plugins | Embla plugins | — |
setApi | (api: CarouselApi) => void | — |
className | string | — |
Also accepts standard div attributes. Renders role="region" and aria-roledescription="carousel".
| Data attribute | Values |
|---|---|
data-slot | carousel |
CarouselContent
Scrollable track (viewport ref for Embla).
| Prop | Type | Default |
|---|---|---|
className | string | — |
Also accepts standard div attributes.
| Data attribute | Values |
|---|---|
data-slot | carousel-viewport on the overflow wrapper; carousel-content on the inner flex track |
CarouselItem
One slide.
| Prop | Type | Default |
|---|---|---|
className | string | — |
Also accepts standard div attributes. Renders role="group" and aria-roledescription="slide".
| Data attribute | Values |
|---|---|
data-slot | carousel-item |
CarouselPrevious
Previous control; disabled at the start unless loop is set in opts. Extends Button props (variant defaults to ghost, size to icon-sm).
| Prop | Type | Default |
|---|---|---|
variant | Button variant | ghost |
size | Button size | icon-sm |
className | string | — |
Also accepts other Button / button props. Default aria-label is Previous slide (supplement with visible labels when needed).
| Data attribute | Values |
|---|---|
data-slot | carousel-previous |
CarouselNext
Next control; disabled at the end unless loop is set in opts. Same Button integration as CarouselPrevious.
| Prop | Type | Default |
|---|---|---|
variant | Button variant | ghost |
size | Button size | icon-sm |
className | string | — |
Also accepts other Button / button props. Default aria-label is Next slide.
| Data attribute | Values |
|---|---|
data-slot | carousel-next |
useCarousel and CarouselApi
useCarousel reads carousel context inside subcomponents. CarouselApi is the Embla instance type exported for setApi and subscriptions.
Accessibility
- Prefer visible labels or
aria-labelonCarouselPrevious/CarouselNextwhen the carousel is not described elsewhere. ThemeProvidersupplies Embladirectionfor RTL; keep slide markup perceivable for keyboard and screen reader users.
Source in the repo: packages/experience-system/src/components/Carousel/Carousel.tsx and useCarousel.ts.