Carousel

A touch-friendly content slider with previous/next navigation. Supports horizontal and vertical orientations, multi-slide views, and looping. Built on Embla Carousel.

Anatomy

<Carousel> is the root — pass opts (Embla options), plugins, and orientation here. CarouselContent is the slide track. Each slide is a CarouselItem — use Tailwind basis-* classes on it to control how many slides are visible at once. CarouselPrevious and CarouselNext are the arrow buttons.

1
2
3
4
5
CarouselContent (slide track)CarouselItem (one slide)CarouselPrevious / CarouselNext

Examples

Multi-slide — two visible at once

A
B
C
D
E
F

Looping — with loop option

Red
Green
Blue
Purple

Vertical orientation

Slide 1
Slide 2
Slide 3
Slide 4

Design Guidelines

Do

  • Use for browsing sequences. Carousels work best for visually similar, ordered content — images, testimonials, product cards — where the user actively wants to browse.
  • Show partial next slide. Peeking the edge of the next slide signals that there's more content to scroll — it's a natural affordance for swiping.
  • Use basis-* for multi-slide layouts. Set basis-1/2 or basis-1/3 on CarouselItem to show multiple slides at once without extra configuration.

Don't

  • Don't auto-play without user consent. Auto-advancing carousels disorient users and violate WCAG 2.1 (motion). If you need it, provide pause controls.
  • Don't use for critical content. Users miss content in carousels. Important information should be always visible, not hidden behind a next button.
  • Don't add too many slides. Beyond 5–7 items the carousel becomes tedious to navigate. Consider a grid layout instead for large collections.

Developer Reference

Accessibility & Behavior

  • The <Carousel> root renders with role="region" and aria-roledescription="carousel". Each CarouselItem has role="group" and aria-roledescription="slide".
  • Arrow keys (Left/Right or Up/Down for vertical) navigate between slides when the carousel is focused.
  • CarouselPrevious and CarouselNext are automatically disabled when there are no more slides to scroll (unless opts.loop is enabled).
  • Access the Embla API via the setApi prop to imperatively control scroll position, listen to events, or build custom indicators.
  • opts accepts any Embla Carousel option: loop, align, skipSnaps, dragFree, etc.

Usage

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselPrevious,
  CarouselNext,
} from "@/components/ui/carousel"

// Basic carousel
<Carousel>
  <CarouselContent>
    {items.map((item) => (
      <CarouselItem key={item.id}>
        <div className="aspect-square rounded-lg bg-muted">{item.label}</div>
      </CarouselItem>
    ))}
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

// Multi-slide: two per view
<Carousel>
  <CarouselContent>
    {items.map((item) => (
      <CarouselItem key={item.id} className="basis-1/2">
        {item.label}
      </CarouselItem>
    ))}
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

// Looping
<Carousel opts={{ loop: true }}>...</Carousel>

// Vertical
<Carousel orientation="vertical">...</Carousel>

// Access Embla API
const [api, setApi] = useState<CarouselApi>()
<Carousel setApi={setApi}>...</Carousel>