Carousel
StableA horizontally or vertically scrolling set of slides with swipe, keyboard and button navigation, built on Embla.
Installation
$ pnpm dlx shadcn@latest add https://www.prfct.dev/r/carousel.jsonUsage
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel"<Carousel aria-label="Product screenshots">
<CarouselContent>
<CarouselItem>…</CarouselItem>
<CarouselItem>…</CarouselItem>
<CarouselItem>…</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>The previous and next buttons sit outside the slides, 48px to the left and right. Leave room for them — or position them yourself with className.
Examples
Multiple per view
Set the slide width with a basis-* class on CarouselItem. Combine it with responsive prefixes to show more slides on wider screens, and opts={{ align: "start" }} to snap slides to the leading edge.
<Carousel opts={{ align: "start" }}>
<CarouselContent>
<CarouselItem className="basis-1/2 sm:basis-1/3">…</CarouselItem>
</CarouselContent>
</Carousel>Spacing
Slides are separated by padding on CarouselItem and a matching negative margin on CarouselContent (16px by default). To change the gap, change both: -ml-2 on the content and pl-2 on each item.
Vertical
orientation="vertical" scrolls on the y-axis. Give CarouselContent a fixed height; the buttons move above and below.
With the API
setApi hands you the Embla instance. Use it to build a slide counter, pagination dots or thumbnails, and to react to select events.
const [api, setApi] = React.useState<CarouselApi>()
React.useEffect(() => {
if (!api) return
const onSelect = () => setCurrent(api.selectedScrollSnap())
api.on("select", onSelect)
return () => {
api.off("select", onSelect)
}
}, [api])
<Carousel setApi={setApi}>…</Carousel>Guidelines
When to use
- To browse a set of peer items where seeing a few at a time is enough: screenshots, templates, product photos, testimonials.
- On touch devices, where swiping through a set is natural.
When not to use
- For content everyone must see. Slides after the first are rarely viewed — put important information on the page.
- For navigation or comparison — use Tabs, a grid, or a Table.
- For a single hero image — just show the image.
Show that there's more
Let the next slide peek in, show a count or dots, and keep the previous button disabled on the first slide. People should never wonder whether there is more.
1 / 4
Autoplay
prfct doesn't autoplay. If you add the Embla autoplay plugin, pause on hover and focus, provide a visible pause button, and don't autoplay at all when prefers-reduced-motion is set. Content that moves on its own for more than five seconds must be pausable (WCAG 2.2.2).
Accessibility
The carousel is a region with aria-roledescription="carousel", and every slide is a group with aria-roledescription="slide".
| Key | Behavior |
|---|---|
Tab | Moves focus into the slides and to the previous and next buttons. |
← | Scrolls to the previous slide when focus is inside the carousel. |
→ | Scrolls to the next slide when focus is inside the carousel. |
EnterSpace | Activates the focused previous or next button. |
- Name the carousel. Give
Carouselanaria-labelthat says what the slides are: "Product screenshots". - Label slides by position. Give each
CarouselItemanaria-labelsuch as "2 of 5", so people know where they are. - Buttons are labeled.
CarouselPreviousandCarouselNextinclude "Previous slide" and "Next slide" for screen readers — translated by LocaleProvider — and are disabled at the ends unless the carousel loops. - Text on images. Keep 4.5:1 contrast for text over imagery — use a scrim, as in the examples.
API reference
Carousel
The root. Accepts all div props.
CarouselContent
The scrolling track. Accepts all div props; its negative margin pairs with the item padding to form the gap.
CarouselItem
A single slide, full width by default. Set basis-* to show several at once.
CarouselPrevious, CarouselNext
Outline icon buttons wired to the carousel. They accept all Button props; variant defaults to "outline" and size to "icon-sm".
useCarousel
Returns { api, scrollPrev, scrollNext, canScrollPrev, canScrollNext, orientation } for building custom controls inside a Carousel.