Carousel
A Carousel shows slides from a set and allows users to move between them with a button, a drag, or an arrow key.
When to Use
- for a set of images or cards of one shape, when the first slide shows what the rest contain
- for a row of cards that extends past its frame, with several slides in view, such as three of five
When Not to Use
- for content that every reader needs, as the slides after the first are hidden until users move to them (lay it out on the page)
- for views that have names, as a slide has no visible label (use Tabs)
- for a row that scrolls freely and stops anywhere (use a Scroll Area with a horizontal scrollbar)
- to move between pages of a list (use Pagination)
Anatomy
| Element | Usage |
|---|---|
| Content | Required |
| Previous button | Required unless another control moves the slides |
| Next button | Required unless another control moves the slides |
| Slide | Required |
Carousel is the region around these parts. It has the same box as the content, and the two buttons are positioned outside it. CarouselContent clips the slides, and each CarouselItem is one slide.
States
The previous button is disabled at the first slide, and the next button at the last slide, unless loop is set. A disabled button keeps focus, so the arrow keys still move the slides.
Behavior
A click on a button or a press of an arrow key moves one slide. When users drag, the slides move with the pointer, and the Carousel settles on a slide when the drag ends. The Carousel starts at the first slide, stops at the last, and never moves by itself.
Slides
A slide has basis-full, which is the width of the content. To show more slides, add a basis class to each CarouselItem; for example, basis-1/3 shows three. By default, align in opts is 'center'. The story with three slides in view sets 'start', which aligns a slide with the start edge after each move. To keep all slides at one shape, use an Aspect Ratio.
Slides are 16px apart. A slide has ps-4, and the row has -ms-4, which pulls the first slide back to the edge. To change the gap, change both classes: ps-2 on each CarouselItem and -ms-2 on CarouselContent result in a gap of 8px.
Buttons
A button is positioned outside one edge of the content, so a Carousel needs 48px of free space on both sides.
mx-12, so that both buttons fit in the frame.Orientation
To stack the slides and move them up and down, set orientation="vertical". The buttons are then positioned above and below the content. Set a height on CarouselContent, as the preview does with h-64, because a column of slides has no height to clip to.
Loop
To join the last slide to the first, set loop: true in opts. Both buttons then stay enabled.
Motion
Embla moves the slides from script, on a physics model. duration in opts is a number with no unit. By default, it is 25, and Embla recommends 20 to 60. A higher number results in a slower scroll.
When users request reduced motion, the slides change immediately.
Right-to-Left
The Carousel reads the direction from Base UI’s DirectionProvider and passes it to Embla. In a right-to-left layout, the slides run from the right, and ArrowLeft moves to the next slide. To set the direction yourself, pass direction in opts. See Right-to-Left for the provider.
Content
| String | Rule | Example | Counterexample |
|---|---|---|---|
Carousel’s aria-label | Name what the slides contain. Omit “carousel”, as the role description already says it. | “Client stories” | “Client stories carousel” |
Slide’s aria-label | Use the slide’s title or its place in the set. Omit “slide” for the same reason. | “2 of 5” | “Slide 2” |
The buttons are named “Previous slide” and “Next slide”, and the role descriptions are “carousel” and “slide”. All four are in labels.carousel. To translate them, use the Summit Provider.
Accessibility
A Carousel is a region that contains groups and two buttons. When a slide is out of view, it stays in the page and in the accessibility tree, and users can reach a control in it with Tab. Embla then scrolls that slide into view.
Keyboard
| Key | Result |
|---|---|
Tab | Moves focus through the controls in the slides, then to the previous button, then to the next button. |
ArrowLeft | Moves to the previous slide when focus is anywhere in the Carousel |
ArrowRight | Moves to the next slide when focus is anywhere in the Carousel |
Enter, Space | Moves one slide when a button has focus. Focus stays on the button. |
A vertical Carousel uses the same two arrow keys; ArrowUp and ArrowDown do nothing.
The Carousel handles both arrow keys before anything inside it. In a text field, a Textarea, or a select, the arrow keys keep their own behavior.
Requirements
A Carousel needs a name so that screen readers can announce the region, as WCAG 2.2 SC 4.1.2 Name, Role, Value requires. To provide
one, set aria-label or aria-labelledby.
A Carousel needs its two buttons, or another control that moves the slides on a click. If it has neither, users who can’t drag have no way to move the slides with a pointer, which WCAG 2.2 SC 2.5.7 Dragging Movements doesn’t allow.
Summit has no autoplay. If a Carousel moves by itself for more than 5 seconds, it needs a control that stops it so
that users who read slowly can finish a slide, as WCAG 2.2 SC 2.2.2 Pause, Stop, Hide requires. This includes a Carousel that uses
Embla’s Autoplay plugin in plugins.
Name each slide as well. The Carousel pattern of the ARIA Authoring Practices Guide names a slide by its title or its place in the set, such as “2 of 5”.
Installation
npx shadcn@latest add @summit/carouselThe CLI also adds @summit/button and @summit/summit-provider.
Usage
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from '@/components/ui/carousel';
<Carousel aria-label="Client stories" className="mx-12 w-64">
<CarouselContent>
<CarouselItem aria-label="1 of 3">Luma Architects</CarouselItem>
<CarouselItem aria-label="2 of 3">Studio Lumen</CarouselItem>
<CarouselItem aria-label="3 of 3">Northwind</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>;Several Slides in View
To show several slides, set a basis on each CarouselItem, and align the slides to the start.
<Carousel opts={{ align: 'start' }} aria-label="Client stories" className="mx-12 w-96">
<CarouselContent>
<CarouselItem className="basis-1/3" aria-label="1 of 5">
Luma Architects
</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Vertical
For a vertical Carousel, set orientation="vertical" on Carousel and a height on CarouselContent.
<Carousel orientation="vertical" aria-label="Client stories" className="my-12 w-64">
<CarouselContent className="h-64">
<CarouselItem aria-label="1 of 3">Luma Architects</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Show the Position
To show the position, pass setApi, which receives Embla’s API once the Carousel is ready. selectedScrollSnap returns the index of the slide in view, and the select event fires when it changes.
import type { CarouselApi } from '@/components/ui/carousel';
import { useEffect, useState } from 'react';
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(1);
useEffect(() => {
if (!api) {
return;
}
const onSelect = () => setCurrent(api.selectedScrollSnap() + 1);
onSelect();
api.on('select', onSelect);
return () => {
api.off('select', onSelect);
};
}, [api]);
<>
<Carousel setApi={setApi} aria-label="Client stories" />
<p>{current} of 5</p>
</>;API Reference
className on CarouselContent is applied to the row, not to the div that clips.
Carousel
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | 'horizontal' | 'vertical' | 'horizontal' | The direction the slides move. It sets Embla’s axis and overrides the one in opts. |
opts | Embla’s options | None | Passed to useEmblaCarousel |
plugins | Embla’s plugins | None | Passed to useEmblaCarousel |
setApi | (api: CarouselApi) => void | None | Receives Embla’s API once it is ready |
Other props are passed to the div. See the Embla Carousel documentation for loop, align, direction, duration, and breakpoints.
CarouselPrevious and CarouselNext
| Prop | Type | Default | Description |
|---|---|---|---|
variant | The variant of a Button | 'outline' | The Button’s variant |
size | The size of a Button | 'icon-sm' | The Button’s size |
Both accept the other props of a Button.
useCarousel
useCarousel returns api, carouselRef, scrollPrev, scrollNext, canScrollPrev, canScrollNext, orientation, and opts. It throws an error when it is called outside a Carousel, so call it from a component rendered inside one, such as a custom button.