Skip to content
shadcn.io
shadcn.io is not affiliated with official shadcn/ui

Shadcn Carousel for React and Tailwind

An interactive slideshow for cycling through items.

Installation

bunx --bun shadcn@latest add https://kit.dev/r/carousel.json

Anatomy

Carousel
├── CarouselControl
│   ├── CarouselPrevious
│   ├── CarouselAutoplayTrigger
│   │   └── CarouselAutoplayIndicator
│   └── CarouselNext
├── CarouselContent
│   └── CarouselItem
├── CarouselIndicatorGroup
│   └── CarouselIndicator
└── CarouselProgressText

Carousel maps to Ark Carousel.Root. CarouselContent is the item group (track). CarouselPrevious / CarouselNext are the prev/next triggers. slideCount is required so snap points can be computed (including SSR).

Usage

import {
  Carousel,
  CarouselContent,
  CarouselControl,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel";
<Carousel slideCount={slides.length}>
  <CarouselControl>
    <CarouselPrevious />
    <CarouselNext />
  </CarouselControl>
  <CarouselContent>
    {slides.map((slide, index) => (
      <CarouselItem index={index} key={slide}>
        {slide}
      </CarouselItem>
    ))}
  </CarouselContent>
</Carousel>

shadcn.io defaults spacing to 16px (Ark: 0px). Prev/next always render as icon Buttons; extra children on those triggers are not used. Accessible names default to "Previous slide" / "Next slide".

The track is a CSS grid. Size and gap come from --slide-item-size and --slide-spacing on the root, not from Tailwind flex / gap-* on CarouselContent.

Controlled

Use page and onPageChange. page is the snap page, not necessarily the item index.

Root Provider

Use useCarousel with CarouselRootProvider when you need the API outside the tree. Pass slideCount (and spacing if you want the shadcn.io default) to useCarousel(), not to the provider.

Orientation

Horizontal

Vertical

orientation="vertical" scrolls the track on the block axis. Prev/next rotate to match.

Thumbnail indicators

Render a preview inside each CarouselIndicator. Thumbnail images can use alt="" when the indicator has an accessible name (aria-label or the default "Go to slide N").

Horizontal

Vertical

Examples

Autoplay

autoplay (default delay 4000ms) with loop. CarouselAutoplayTrigger toggles play/pause. Zag also turns loop on when autoplay is set unless you pass loop yourself.

Pause on hover

Call pause() / play() from CarouselContext on pointer enter/leave. This is not a built-in prop.

Loop

Slides per page

slidesPerPage shows more than one slide. Render indicators from pageSnapPoints, not from every item.

Spacing

shadcn.io default is 16px. Combine with slidesPerPage (including fractional values such as 1.5) to peek at adjacent slides.

Variable sizes

autoSize lets each item set its own width. Use snapAlign per item. Give the item a width (className="w-auto" plus a sized child).

Mouse drag

Scroll to slide

scrollToIndex(index) jumps to an item. scrollTo(page) jumps to a page.

Dynamic slides

Keep slideCount and page in sync when the list grows or shrinks. The machine refreshes snap points when slideCount changes.

Guides

Pages vs slides

page is the snap page, not the item index. With slidesPerPage={2}, two items share a page. Indicators should map carousel.pageSnapPoints.

CSS variables

Set on the root by the machine. The item group uses them for grid sizing and gap:

CSS variableDescription
--slides-per-pageVisible slides per page
--slide-spacingGap between slides (spacing prop)
--slide-item-sizecalc(100% / var(--slides-per-page) - var(--slide-spacing) * (var(--slides-per-page) - 1) / var(--slides-per-page)), or auto when autoSize

API Reference

shadcn.io wraps Ark UI Carousel. Defaults below are shadcn.io values. spacing defaults to 16px (Ark: 0px).

asChild merges props onto a single child element.

Root. Renders a div with role="region" and aria-roledescription="carousel".

PropTypeDefaultDescription
slideCountnumberrequiredTotal slides. Needed for snap points and SSR.
allowMouseDragbooleanfalseScroll by dragging with the mouse.
asChildbooleanfalseRender the child element instead of a div.
autoplayboolean | { delay: number }falseAuto-scroll. Default delay is 4000ms.
autoSizebooleanfalseVariable-width slides. Item group uses display: flex instead of grid.
classNamestring-Class names on the root.
defaultPagenumber0Uncontrolled initial page.
dir"ltr" | "rtl"-Text direction. Usually inherited from LocaleProvider.
idstring-Unique id for the carousel machine.
idsPartial<{ root: string; item: (index: number) => string; itemGroup: string; nextTrigger: string; prevTrigger: string; indicatorGroup: string; indicator: (index: number) => string }>-Element ids for composition.
inViewThresholdnumber | number[]0.6How much of an item must be visible to count as in view.
loopbooleanfalseWrap from last page to first. Defaults to true in Zag when autoplay is set and you omit loop.
onAutoplayStatusChange(details: AutoplayStatusDetails) => void-Autoplay started or stopped. { type, page, isPlaying }. type is "autoplay.start" | "autoplay" | "autoplay.stop".
onDragStatusChange(details: DragStatusDetails) => void-Drag started or ended. { type, page, isDragging }. type is "dragging.start" | "dragging" | "dragging.end".
onPageChange(details: PageChangeDetails) => void-Page changed. { page, pageSnapPoint }.
orientation"horizontal" | "vertical""horizontal"Scroll axis.
paddingstring-Extra space around the scrollport so neighbors stay partly visible (scroll-padding / padding on the track).
pagenumber-Controlled page.
slidesPerMovenumber | "auto""auto"Slides to move per navigation. auto follows slidesPerPage.
slidesPerPagenumber1Slides visible at once. Can be fractional (for example 1.5).
snapType"proximity" | "mandatory""mandatory"CSS scroll-snap type.
spacingstring"16px"Gap between items.
translationsIntlTranslationssee belowLocalized strings for triggers, items, indicators, autoplay, and progress.

Default translations:

KeyDefault
nextTrigger"Next slide"
prevTrigger"Previous slide"
indicator"Go to slide {n}"
item"{n} of {count}"
autoplayStart"Start slide rotation"
autoplayStop"Stop slide rotation"
progressText"{page} / {totalPages}"

shadcn.io prev/next buttons also set aria-label to "Previous slide" / "Next slide". Override those labels on the trigger if you localize via translations.

AttributeDescription
data-slotcarousel
data-scopecarousel
data-partroot
data-orientation"horizontal" or "vertical"
CSS variableDescription
--slides-per-pageVisible slides per page
--slide-spacingGap between slides
--slide-item-sizeComputed slide size

CarouselRootProvider

Takes the API from useCarousel. Renders a div with the same root attributes as Carousel.

PropTypeDefaultDescription
valueUseCarouselReturnrequiredReturn value of useCarousel().
asChildbooleanfalseRender the child element instead of a div.
classNamestring-Class names on the root.

Pass slideCount, spacing, autoplay, orientation, and other machine options to useCarousel(), not to CarouselRootProvider.

AttributeDescription
data-slotcarousel
data-scopecarousel
data-partroot
data-orientation"horizontal" or "vertical"

CarouselContent

Item group (the track). Renders a div. aria-live is "polite" while idle and "off" while autoplay is running.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the track.
AttributeDescription
data-slotcarousel-group
data-scopecarousel
data-partitem-group
data-orientation"horizontal" or "vertical"
data-draggingPresent while dragging

CarouselItem

One slide. Renders a div with role="group" and aria-roledescription="slide". index is required.

PropTypeDefaultDescription
indexnumberrequiredSlide index.
snapAlign"start" | "center" | "end""start"Where the item snaps. Ignored when this index is not a snap point.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the item. Use w-auto (and a sized child) with autoSize.
AttributeDescription
data-slotcarousel-item
data-scopecarousel
data-partitem
data-indexSlide index
data-inviewPresent when in the viewport
data-orientation"horizontal" or "vertical"

aria-hidden is set when the item is not in view. aria-label defaults to "{n} of {count}".

CarouselControl

Layout wrapper for prev/next (and optional autoplay). Renders a div.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the control.
AttributeDescription
data-slotcarousel-control
data-scopecarousel
data-partcontrol
data-orientation"horizontal" or "vertical"

CarouselPrevious / CarouselNext

Icon buttons. Always asChild onto Button (size="icon-md", variant="outline", pill). Default aria-label is "Previous slide" / "Next slide". Chevrons are aria-hidden. Extra children are not rendered.

PropTypeDefaultDescription
classNamestring-Position/offset of the trigger.
asChildbooleantrueAlways merged onto the inner Button.
AttributeDescription
data-slotcarousel-previous / carousel-next
data-scopecarousel
data-partprev-trigger / next-trigger
data-orientation"horizontal" or "vertical"

aria-controls points at the item group. The button is disabled when that direction cannot scroll (loop keeps both enabled).

CarouselIndicatorGroup

Container for dots or thumbnails. Renders a div. Arrow keys on this group change the page.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the group.
AttributeDescription
data-slotcarousel-indicator-group
data-scopecarousel
data-partindicator-group
data-orientation"horizontal" or "vertical"

CarouselIndicator

One page control. Renders a button. index is required.

PropTypeDefaultDescription
indexnumberrequiredPage index (use pageSnapPoints when slidesPerPage is not 1).
readOnlybooleanfalseDisplay-only; not clickable.
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the indicator.
AttributeDescription
data-slotcarousel-indicator
data-scopecarousel
data-partindicator
data-currentPresent for the active page
data-indexIndicator index
data-readonlyPresent when read-only
data-orientation"horizontal" or "vertical"

Default aria-label is "Go to slide {n}".

CarouselAutoplayTrigger

Toggles autoplay. Always asChild onto Button unless you pass your own child. Default control is an icon button with play/pause icons.

PropTypeDefaultDescription
asChildbooleantrueMerge onto a single child.
classNamestring-Class names on the trigger.
AttributeDescription
data-slotcarousel-autoplay-trigger
data-scopecarousel
data-partautoplay-trigger
data-orientation"horizontal" or "vertical"
data-pressedPresent when autoplay is on

Zag sets aria-label to "Stop slide rotation" / "Start slide rotation". The default shadcn.io button also sets aria-label="Toggle autoplay".

CarouselAutoplayIndicator

Shows its children while autoplay is playing and fallback while paused. Renders a span.

PropTypeDefaultDescription
fallbackReactNode-Content when autoplay is paused.
asChildbooleanfalseMerge onto a single child.
AttributeDescription
data-slotcarousel-autoplay-indicator
data-scopecarousel
data-partautoplay-indicator

CarouselProgressText

"current / total" page text. Renders a span. Pass children to replace the default label.

PropTypeDefaultDescription
asChildbooleanfalseMerge onto a single child.
classNamestring-Class names on the text.
AttributeDescription
data-slotcarousel-progress-text
data-scopecarousel
data-partprogress-text

useCarousel

Creates the carousel API for CarouselRootProvider. Accepts the same machine options as Carousel except layout-only props (asChild, className).

const carousel = useCarousel({ slideCount: 5, spacing: "16px" });
carousel.scrollToIndex(3);

CarouselContext / useCarouselContext

Render-prop or hook access. Use inside Carousel or CarouselRootProvider.

PropertyTypeDescription
pagenumberCurrent page.
pageSnapPointsnumber[]Snap offsets (one per page).
isPlayingbooleanWhether autoplay is running.
isDraggingbooleanWhether the pointer is dragging (allowMouseDrag).
canScrollNext / canScrollPrevbooleanWhether navigation in that direction is possible.
scrollToIndex(index: number, instant?: boolean) => voidJump to an item.
scrollTo(page: number, instant?: boolean) => voidJump to a page.
scrollNext / scrollPrev(instant?: boolean) => voidMove one page.
getProgress() => numberpage / pageSnapPoints.length (0 on the first page).
getProgressText() => stringLocalized progress label from translations.progressText.
play / pause() => voidControl autoplay.
isInView(index: number) => booleanWhether an item is in view.
refresh() => voidRecompute snap points (after slide count or size changes).

CarouselContext children: (context) => ReactNode.

Accessibility

Complies with the Carousel WAI-ARIA design pattern.

The root is a labeled region (role="region", aria-roledescription="carousel"). Each item is a slide (role="group", aria-roledescription="slide"). Off-screen slides are aria-hidden. The track uses aria-live="polite" unless autoplay is playing (aria-live="off").

Prev/next and autoplay are named buttons. Decorative chevrons and play/pause icons are aria-hidden. Prefer loop plus a pause control (CarouselAutoplayTrigger or pause() / play()) when using autoplay. Honor reduced motion: avoid autoplay or use a long delay.

Images need alt. Thumbnail indicators that repeat the same image can use alt="" if the control has an accessible name.

Keyboard support

KeyDescription
TabMove to the next control (prev, next, autoplay, or an indicator).
Shift + TabMove to the previous control.
Enter / SpaceActivate the focused trigger or indicator.
ArrowRightNext page when focus is in the indicator group (horizontal). Respects RTL.
ArrowLeftPrevious page (horizontal indicators).
ArrowDownNext page (vertical indicators).
ArrowUpPrevious page (vertical indicators).
HomeFirst page (indicator group).
EndLast page (indicator group).

Touch swipe always moves the track. Mouse drag requires allowMouseDrag.