Carousel
A slide track with previous and next controls, built on Embla Carousel.
Carousel wraps embla-carousel-react in a track, a slide and two icon controls. Each slide takes the full width of the track, and the controls scroll the track one slide at a time. Arrow keys work when the track has focus.
It is an Authored component: shadcn/ui owns its structure and its design comes
from the nearest Port, because coss/ui ships no carousel. The controls inherit
their surface, focus ring and disabled treatment from Button, and a slide
inherits its sizing from AspectRatio.
Installation
npx shadcn@latest add @kosori/carouselExample
import { AspectRatio } from '@/components/ui/aspect-ratio';
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
const SLIDES = [
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-primary/16 to-background',
description: 'The first slide of the default carousel.',
title: 'One',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-info/16 to-background',
description: 'Slides scroll with the previous and next controls.',
title: 'Two',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-success/16 to-background',
description: 'The track also responds to the arrow keys.',
title: 'Three',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-warning/16 to-background',
description: 'Every slide shares the same width.',
title: 'Four',
},
{
className:
'overflow-hidden rounded-xl border bg-linear-to-br from-destructive/16 to-background',
description: 'The last slide disables the next control.',
title: 'Five',
},
];
const CarouselExample = () => (
<div className='w-full max-w-md px-12'>
<Carousel>
<CarouselContent>
{SLIDES.map((slide) => (
<CarouselItem key={slide.title}>
<AspectRatio className={slide.className} ratio={16 / 9}>
<div className='flex size-full flex-col justify-end gap-1 p-5'>
<span className='font-medium text-lg'>{slide.title}</span>
<span className='text-muted-foreground text-sm'>
{slide.description}
</span>
</div>
</AspectRatio>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
</div>
);
export default CarouselExample;
Usage
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from '@/components/ui/carousel';
const CarouselDemo = () => (
<Carousel className='w-full max-w-sm'>
<CarouselContent>
<CarouselItem>Slide one</CarouselItem>
<CarouselItem>Slide two</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);The previous and next controls sit outside the track, so leave room for them:
the Example pads its container by px-12.
Orientation
orientation switches the scroll axis. The controls rotate and move above and
below the track, so a vertical carousel needs vertical room instead:
<Carousel className='h-64 w-full max-w-xs' orientation='vertical'>
<CarouselContent>
<CarouselItem>Slide one</CarouselItem>
<CarouselItem>Slide two</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>API
| Prop | Type | Default | Purpose |
|---|---|---|---|
opts | Embla options | undefined | Forwarded to Embla Carousel |
orientation | horizontal, vertical | horizontal | Scroll axis |
plugins | Embla plugins | undefined | Forwarded to Embla Carousel |
setApi | (api: CarouselApi) => void | undefined | Receives the Embla API |
useCarousel reads the nearest carousel's API and scroll state, and throws
outside a Carousel.