Carousel
A carousel with motion and swipe gestures built on Embla Carousel.
CMS Demo
See how content editors configure carousels in a CMS:
Payload Block Example
// In your Payload CMS blocks config
export const CarouselBlock = {
slug: "carousel",
fields: [
{
name: "slides",
type: "array",
minRows: 1,
fields: [
{
name: "image",
type: "upload",
relationTo: "media",
required: true,
},
{ name: "alt", type: "text", localized: true },
{ name: "caption", type: "text", localized: true },
],
},
{
name: "options",
type: "group",
fields: [
{ name: "loop", type: "checkbox", defaultValue: false },
{ name: "autoplay", type: "checkbox", defaultValue: false },
{
name: "autoplayDelay",
type: "number",
defaultValue: 3000,
admin: {
condition: (data, siblingData) => siblingData?.autoplay,
},
},
],
},
],
}Rendering the Carousel
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
} from "@mordecai-design-system/ui"
export function ImageCarousel({ slides, options }) {
return (
<Carousel opts={{ loop: options.loop }}>
<CarouselContent>
{slides.map((slide, index) => (
<CarouselItem key={index}>
<img
src={slide.image.url}
alt={slide.alt}
className="w-full rounded-lg"
/>
{slide.caption && (
<p className="mt-2 text-center text-muted-foreground">
{slide.caption}
</p>
)}
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
)
}Basic Usage
Installation
pnpm add @corew500/uiUsage
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselPrevious,
CarouselNext,
} from "@mordecai-design-system/ui"<Carousel>
<CarouselContent>
<CarouselItem>Slide 1</CarouselItem>
<CarouselItem>Slide 2</CarouselItem>
<CarouselItem>Slide 3</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Examples
With Loop
<Carousel opts={{ loop: true }}>
<CarouselContent>
{items.map((item, index) => (
<CarouselItem key={index}>{item}</CarouselItem>
))}
</CarouselContent>
</Carousel>Vertical Orientation
<Carousel orientation="vertical" className="h-[200px]">
<CarouselContent>
<CarouselItem>Item 1</CarouselItem>
<CarouselItem>Item 2</CarouselItem>
</CarouselContent>
</Carousel>Multiple Items Per View
<Carousel>
<CarouselContent className="-ml-2">
{items.map((item, index) => (
<CarouselItem key={index} className="basis-1/3 pl-2">
{item}
</CarouselItem>
))}
</CarouselContent>
</Carousel>Loading State
Show skeleton placeholders while slides are loading:
const { data, isLoading } = useQuery({ queryKey: ["slides"], queryFn: fetchSlides })
<Carousel loading={isLoading} loadingItems={3}>
<CarouselContent>
{data?.map((slide, index) => (
<CarouselItem key={index}>
<img src={slide.url} alt={slide.alt} />
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>| Prop | Type | Default | Description |
|------|------|---------|-------------|
| loading | boolean | false | Show skeleton loading state |
| loadingItems | number | 3 | Number of skeleton slides to display |
| loadingItemRenderer | (index) => ReactNode | - | Custom skeleton slide renderer |
Custom skeleton renderer:
<Carousel
loading
loadingItems={4}
loadingItemRenderer={(index) => (
<div className="space-y-2">
<Skeleton className="h-[300px] w-full rounded-xl" />
<Skeleton className="h-4 w-1/2" />
</div>
)}
>
{/* Content */}
</Carousel>Sub-components
| Component | Description |
|-----------|-------------|
| Carousel | Root container with context |
| CarouselContent | Scrollable container for items |
| CarouselItem | Individual slide |
| CarouselPrevious | Previous slide button |
| CarouselNext | Next slide button |
Props
Carousel
| Prop | Type | Default | Description |
|---|---|---|---|
| opts | Partial<OptionsType> | — | — |
| plugins | CreatePluginType<LoosePluginType, {}>[] | — | — |
| orientation | enum | horizontal | — |
| setApi | (api: EmblaCarouselType) => void | — | — |
| loading | enum | false | Show skeleton loading state |
| loadingItems | number | 3 | Number of skeleton items to show when loading (default: 3) |
| loadingItemRenderer | (index: number) => ReactNode | — | Custom skeleton item renderer |
CarouselItem
CarouselItem accepts all standard div element props. Use className for sizing (e.g., basis-1/3 for multiple items per view).
Hooks
useCarousel
Access carousel context from child components:
function SlideCounter() {
const { api, canScrollPrev, canScrollNext } = useCarousel()
// Use carousel state
}