Skip to content

shadcn.io is an independent project built on shadcn/ui

Free

Carousel with Thumbnails for React

Five numbered slides and a row of buttons 1 through 5 under them. The current button is filled, and there are no arrow buttons.

A wide frame shows one number, from 1 through 5. Under it, five buttons read 1 through 5. The button for the current slide is filled, and the others are outlines.

Use numbered buttons when people should jump to a slide. A Tabs row fits if each slide needs a name instead of a number. On React, Base UI does not mark the current button.

Preview

Preview width

Installation

Sign in to get your install command
Free with an account. It carries your token, so the CLI installs everything in one go.
Use email insteadNo credit card

Anatomy

Carousel anatomy: content, item, previous, next, indicator group

Usage

<div className="mx-auto flex w-full max-w-md flex-col gap-4">
  <Carousel setApi={setApi}>
    <CarouselContent>
      {slides.map((slide) => (
        <CarouselItem key={slide}>
          <div className="flex aspect-2/1 items-center justify-center rounded-md border bg-background">
            <span className="font-semibold text-4xl">{slide}</span>
          </div>
        </CarouselItem>
      ))}
    </CarouselContent>
  </Carousel>
  <div className="flex justify-center gap-2">
    {slides.map((slide, index) => (
      <Button
        aria-current={current === index ? "true" : undefined}
        aria-label={`Go to slide ${slide}`}
        data-index={index}
        key={slide}
        onClick={handleThumbnailClick}
        variant={current === index ? "default" : "outline"}
      >
        {slide}
      </Button>
    ))}
  </div>
</div>

FAQ

How do the buttons stay in sync with the slides?

setApi stores the carousel's API. An effect listens to its select event and stores selectedScrollSnap() in current, and each button calls scrollTo with its index.

What does each slide show?

Five slides. Each is an aspect-2/1 box with rounded-md border bg-background and the slide number in font-semibold text-4xl. The carousel and the buttons stack in a max-w-md column with gap-4.

How are the buttons styled?

A flex justify-center gap-2 row of Buttons, one per slide. The current one is variant="default", the others variant="outline". Change those, or the size, to restyle them.

Can I show pictures instead of numbers?

Yes. Put a small img in each button, with a fixed size such as size-12 and object-cover, and keep the aria-label so each button still has a name.

What if there are too many buttons for the row?

Make the row scroll: overflow-x-auto instead of justify-center. For more than ten or so slides, a page counter (Slide 3 of 24) is easier to use than a long row of buttons.

What will a screen reader say?

Each button's aria-label reads Go to slide 1 through Go to slide 5, and the current one has aria-current, so it's announced as current. Previous and next are left out here: the buttons replace them.

API Reference

Sets setApi={setApi}. The rest is on the Carousel page.

Button

Sets aria-current={current === index ? "true" : undefined}, aria-label, Go, to, slide and slide. The rest is on the Button page.