Skip to content

Navigation

Carousel

A labelled strip of slides shown one at a time with previous and next controls.

When to use it: Use it to feature a few highlights in limited space; use Tabs when people pick sections by name.

On this page

Example

Carousel.tsxtsx
import {
  Carousel,
  CarouselAutoplayToggle,
  CarouselNext,
  CarouselPrevious,
  CarouselSlide,
  CarouselViewport,
} from "@comp0/react";
import { ChevronLeftIcon, ChevronRightIcon, PlayPauseIcon } from "@heroicons/react/16/solid";

export function Example() {
  return (
    <Carousel
      aria-label="Featured recipes"
      loop
      autoplay={5000}
      className="w-full max-w-sm rounded border border-zinc-950/10 p-3 dark:border-white/10"
    >
      <div className="mb-2 flex items-center gap-1">
        <CarouselAutoplayToggle className="rounded px-3 py-2.5 text-zinc-700 hover:bg-zinc-100 data-stopped:text-teal-700 sm:py-2 dark:text-zinc-300 dark:hover:bg-zinc-800 dark:data-stopped:text-teal-300">
          <PlayPauseIcon className="size-4" aria-hidden="true" />
        </CarouselAutoplayToggle>
        <CarouselPrevious className="ml-auto rounded px-3 py-2.5 text-zinc-700 hover:bg-zinc-100 disabled:opacity-40 sm:py-2 dark:text-zinc-300 dark:hover:bg-zinc-800">
          <ChevronLeftIcon className="size-4" aria-hidden="true" />
        </CarouselPrevious>
        <CarouselNext className="rounded px-3 py-2.5 text-zinc-700 hover:bg-zinc-100 disabled:opacity-40 sm:py-2 dark:text-zinc-300 dark:hover:bg-zinc-800">
          <ChevronRightIcon className="size-4" aria-hidden="true" />
        </CarouselNext>
      </div>
      <CarouselViewport className="overflow-hidden rounded">
        <div
          className="flex transition-transform duration-300"
          style={{ transform: "translateX(calc(var(--comp0-carousel-index) * -100%))" }}
        >
          <CarouselSlide className="flex h-32 w-full shrink-0 items-center justify-center bg-teal-50 text-base font-medium text-teal-900 sm:text-sm dark:bg-teal-950 dark:text-teal-100">
            Roasted tomato soup
          </CarouselSlide>
          <CarouselSlide className="flex h-32 w-full shrink-0 items-center justify-center bg-zinc-100 text-base font-medium text-zinc-900 sm:text-sm dark:bg-zinc-800 dark:text-zinc-100">
            Charred corn salad
          </CarouselSlide>
          <CarouselSlide className="flex h-32 w-full shrink-0 items-center justify-center bg-teal-100 text-base font-medium text-teal-950 sm:text-sm dark:bg-teal-900 dark:text-teal-50">
            Smoky bean stew
          </CarouselSlide>
        </div>
      </CarouselViewport>
    </Carousel>
  );
}

Anatomy

Dashed frames are invisible state providers; shaded shapes own real DOM. Numbered pins match the list below.

A wireframe sketch of the assembled component. Each numbered marker matches a part in the list that follows.

  1. Carousel

    Section with the carousel roledescription; owns the slide index and the autoplay pause bookkeeping. Owns a DOM element.

  2. CarouselViewport

    Wraps the slides; exposes --comp0-carousel-index for transform tracks and flips aria-live between off (rotating) and polite (paused or stopped). Owns a DOM element.

  3. CarouselSlide

    One slide, announced as a group with the slide roledescription. Owns a DOM element.

  4. CarouselPreviousOptional

    Native button that shows the previous slide; disabled on the first slide unless loop. Owns a DOM element.

  5. CarouselNextOptional

    Native button that shows the next slide; disabled on the last slide unless loop. Owns a DOM element.

  6. CarouselAutoplayToggleOptional

    Native button that stops and restarts auto-rotation; renders nothing without autoplay. Owns a DOM element.

Step by step

  1. 1

    Add the main part

    Wrap everything in Carousel with an aria-label, and put the slides in CarouselViewport.

  2. 2

    Add the supporting parts

    Add CarouselPrevious and CarouselNext, plus CarouselAutoplayToggle when autoplay is set.

  3. 3

    Make the behavior clear

    Pass autoplay in milliseconds only with the toggle present; rotation pauses on hover or focus and the toggle stops it for good.

    Exampletsx
    <Carousel aria-label="Featured recipes" autoplay={5000}><CarouselAutoplayToggle /><CarouselPrevious /><CarouselNext /><CarouselViewport><CarouselSlide>Soup</CarouselSlide></CarouselViewport></Carousel>

Keyboard

Moves through the rotation, previous, and next controls.
Presses the focused carousel control.
Space
Presses the focused carousel control.

Forms and accessibility

A carousel does not create form values.

Accessibility checklist

  • Give the carousel an aria-label naming the content, such as Featured recipes.
  • Keep CarouselAutoplayToggle first in the tab order whenever autoplay is set; hover and focus pauses are temporary, the toggle is the guaranteed stop (WCAG 2.2.2).
  • Slide changes announce politely only while rotation is paused or stopped; name slides after their content with aria-label when N of M is too vague.

API reference

Importtsx
import { Carousel, CarouselAutoplayToggle, CarouselNext, CarouselPrevious, CarouselSlide, CarouselViewport } from "@comp0/react";

Carousel

DOM element

Section with the carousel roledescription; owns the slide index and the autoplay pause bookkeeping.

PropTypeDescription
aria-labelstringNames the carousel for assistive technology; required.
index / defaultIndexnumberControlled or initial slide index.
onIndexChange(index: number) => voidReceives the next slide index.
loopbooleanPrevious on the first slide and Next on the last wrap around.
autoplaynumberMilliseconds between automatic advances; rotation pauses on hover or focus and stops via the toggle.

CarouselViewport

DOM element

Wraps the slides; exposes --comp0-carousel-index for transform tracks and flips aria-live between off (rotating) and polite (paused or stopped).

PropTypeDescription
styleCSSPropertiesMerged under the exposed --comp0-carousel-index variable.

CarouselSlide

DOM element

One slide, announced as a group with the slide roledescription.

PropTypeDescription
aria-labelstringNames the slide after its content; defaults to its computed "N of M" position.

CarouselPrevious

OptionalDOM element

Native button that shows the previous slide; disabled on the first slide unless loop.

PropTypeDescription
aria-labelstringAccessible name; defaults to "Previous slide".

CarouselNext

OptionalDOM element

Native button that shows the next slide; disabled on the last slide unless loop.

PropTypeDescription
aria-labelstringAccessible name; defaults to "Next slide".

CarouselAutoplayToggle

OptionalDOM element

Native button that stops and restarts auto-rotation; renders nothing without autoplay.

PropTypeDescription
aria-labelstringDefaults to "Pause carousel" while rotating and "Play carousel" once stopped.

Style hooks

Attributes that appear while a state is true. Target them with Tailwind data variants such as data-open:bg-zinc-100, or with any CSS selector.

[data-current]

on CarouselSlide

This slide is showing.
[data-rotating]

on Carousel

Auto-rotation is advancing.
[data-stopped]

on Carousel, CarouselAutoplayToggle

The toggle stopped auto-rotation.
:disabled

on CarouselPrevious, CarouselNext

The bound was reached without loop.

Keep exploring