Summit
ComponentsLayout

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

1
2
3
ElementUsage
ContentRequired
Previous buttonRequired unless another control moves the slides
Next buttonRequired unless another control moves the slides
SlideRequired

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.

DoGive the Carousel 48px of margin on each side, for example with mx-12, so that both buttons fit in the frame.
Don’tWhen the Carousel fills a frame that clips, the buttons are cut off, so users with a pointer can only drag the slides.

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

StringRuleExampleCounterexample
Carousel’s aria-labelName what the slides contain. Omit “carousel”, as the role description already says it.“Client stories”“Client stories carousel”
Slide’s aria-labelUse 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

KeyResult
TabMoves focus through the controls in the slides, then to the previous button, then to the next button.
ArrowLeftMoves to the previous slide when focus is anywhere in the Carousel
ArrowRightMoves to the next slide when focus is anywhere in the Carousel
Enter, SpaceMoves 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/carousel

The 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.

Several Slides in View
<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.

Vertical
<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.

Show the Position
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.

PropTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'The direction the slides move. It sets Embla’s axis and overrides the one in opts.
optsEmbla’s optionsNonePassed to useEmblaCarousel
pluginsEmbla’s pluginsNonePassed to useEmblaCarousel
setApi(api: CarouselApi) => voidNoneReceives 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

PropTypeDefaultDescription
variantThe variant of a Button'outline'The Button’s variant
sizeThe 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.

On this page