A swipeable set of slides with previous and next controls.

1
2
3
4
5
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from '@/components/ui/carousel'

export default function CarouselDemo() {
  return (
    <Carousel className="w-full max-w-xs" aria-label="Numbered slides">
      <CarouselContent>
        {Array.from({ length: 5 }, (_, index) => (
          <CarouselItem key={index} aria-label={`${index + 1} of 5`}>
            <div className="flex aspect-square items-center justify-center rounded-lg border border-border bg-card p-6">
              <span className="text-4xl font-semibold">{index + 1}</span>
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
    </Carousel>
  )
}

Installation

pnpm dlx shadcn@latest add https://kagadmodyaa.vercel.app/r/carousel.json

Needs a React + Tailwind project set up with shadcn. Copies the component source straight into your project, so you can edit it.

Carousel imports cn() from lib/utils.ts — add it too if you don't have it yet:

import { clsx, type ClassValue } from 'clsx'
import { twMerge } from 'tailwind-merge'

export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs))
}

Its previous and next controls are Buttons. The CLI installs Button for you; if you copy manually, add components/ui/button.tsx too.

Usage

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

<Carousel className="w-full max-w-xs" aria-label="Featured work">
  <CarouselContent>
    <CarouselItem>Slide 1</CarouselItem>
    <CarouselItem>Slide 2</CarouselItem>
    <CarouselItem>Slide 3</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

// Several at once: set a basis on each item
<CarouselItem className="basis-1/2 lg:basis-1/3">…</CarouselItem>

// Vertical: give the content a height
<Carousel orientation="vertical">
  <CarouselContent className="h-72">…</CarouselContent>
</Carousel>

Examples

Several slides at once

1
2
3
4
5
6
7
8
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from '@/components/ui/carousel'

export default function CarouselSizes() {
  return (
    <Carousel opts={{ align: 'start' }} className="w-full max-w-sm" aria-label="Slides, several at a time">
      <CarouselContent>
        {Array.from({ length: 8 }, (_, index) => (
          <CarouselItem key={index} aria-label={`${index + 1} of 8`} className="basis-1/2 lg:basis-1/3">
            <div className="flex aspect-square items-center justify-center rounded-lg border border-border bg-card p-4">
              <span className="text-2xl font-semibold">{index + 1}</span>
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
    </Carousel>
  )
}

Vertical

1
2
3
4
5
import { Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext } from '@/components/ui/carousel'

export default function CarouselVertical() {
  return (
    <Carousel orientation="vertical" opts={{ align: 'start' }} className="w-full max-w-xs" aria-label="Vertical slides">
      <CarouselContent className="h-[200px]">
        {Array.from({ length: 5 }, (_, index) => (
          <CarouselItem key={index} aria-label={`${index + 1} of 5`} className="basis-1/2">
            <div className="flex h-full items-center justify-center rounded-lg border border-border bg-card p-4">
              <span className="text-3xl font-semibold">{index + 1}</span>
            </div>
          </CarouselItem>
        ))}
      </CarouselContent>
      <CarouselPrevious />
      <CarouselNext />
    </Carousel>
  )
}

API Reference

NameTypeDefaultDescription
orientation'horizontal' | 'vertical''horizontal'The scroll direction. A vertical carousel needs a height on CarouselContent.
optsEmblaOptions—Embla's options: loop, align, slidesToScroll, dragFree, startIndex, and so on.
pluginsEmblaPlugin[]—Embla plugins, such as autoplay.
setApi(api: CarouselApi) => void—Receives the Embla API — read the selected slide, listen for changes or scroll from your own controls.
aria-labelstring—Names the carousel region for screen readers. Give each CarouselItem an aria-label such as "2 of 5" too.
CarouselItem classNamestring—Set the slide size with a basis class (basis-1/2, basis-1/3, …). One slide fills the view by default.
CarouselPrevious / CarouselNextButtonProps—Step buttons, disabled at either end (unless loop is on). Accept Button props such as variant.
Keyboard——With the carousel focused, ArrowLeft and ArrowRight (ArrowUp and ArrowDown when vertical) change slide. Drag or swipe also works.
Reduced motion——When the visitor prefers reduced motion, the buttons and arrow keys jump to the slide instead of scrolling.