Skip to content

Carousel

A slide carousel with previous/next buttons and dots, horizontal or vertical.

1
2
3
4
5

Install

Bash
npx @madeui/cli add carousel

Installs embla-carousel-react alongside the component.

Usage

TSX
import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from '@/components/ui/carousel';
TSX
<Carousel>
  <CarouselContent>
    <CarouselItem>…</CarouselItem>
    <CarouselItem>…</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

Composition

TSX
<Carousel>
  <CarouselContent>
    <CarouselItem />
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
  <CarouselDots />
</Carousel>

Sizes

Set flexBasis on CarouselItem through style to show several slides per view.

1
2
3
4
5
6

Spacing

The default gap is a negative margin on CarouselContent paid back as padding on each CarouselItem. Override both together: marginLeft and paddingLeft for horizontal carousels, marginTop and paddingTop for vertical ones.

1
2
3
4
5
6

Orientation

Vertical carousels need a fixed height on CarouselContent; ArrowUp/ArrowDown replace ArrowLeft/ArrowRight.

1
2
3
4
5

Dots

1
2
3
4
5

API

Pass setApi to get the Embla instance and subscribe to its events.

1
2
3
4
5

Slide 1 of 5

Autoplay

Autoplay is an Embla plugin from a separate package, embla-carousel-autoplay, that you install yourself and pass through plugins.

Bash
npm install embla-carousel-autoplay

Autoplay attaches its mouse listeners to the scroll viewport, which does not contain the previous/next buttons. Pass rootNode: (emblaRoot) => emblaRoot.parentElement so hovering the buttons pauses it too. Its stopOnFocusIn only reacts to focus landing on a slide, so pause and resume from onFocus/onBlur on Carousel as well — without that a keyboard user has no way to stop the slides.

1
2
3
4
5

Loop

1
2
3
4
5

API reference

Built on Embla Carousel. opts and plugins are passed straight to Embla — see the Embla options and plugins references. The carousel has no variant or size of its own; the navigation buttons take Button’s.

The root is a region with aria-roledescription="carousel" and a default aria-label of “Carousel” — override it with your own aria-label when a page holds more than one. Each item is a group with aria-roledescription="slide"; give slides an aria-label such as "1 of 5" when their content does not name them.

Arrow keys scroll to the previous/next slide while focus is inside the carousel. They are ignored when the focused element is an input, textarea, select, or contenteditable, so form controls on a slide keep their own arrow-key behaviour.

Button placement

CarouselPrevious and CarouselNext sit in the gutter outside the viewport from 640px up, and overlay the leading/trailing edge of the track below that, where there is no gutter to hang in. Leave space.s12 of room on the sides (horizontal) or above and below (vertical) at the wider sizes — the vertical example does this with marginBlock. Override left/right/top/bottom through style to place them somewhere else.

PropTypeDefaultDescription
optsCarouselOptions—Embla options (loop, align, dragFree, …). axis is derived from orientation.
pluginsCarouselPlugins—Embla plugins, e.g. Autoplay().
orientation'horizontal' | 'vertical''horizontal'
setApi(api: CarouselApi) => void—Receives the Embla API once the carousel has initialised.
styleStyleXStyles—StyleX styles merged last — always win over the component’s own styles.

CarouselApi is Embla’s carousel instance type, exported for setApi callbacks and useState<CarouselApi>(). useCarousel() exposes the same context the parts use: api, orientation, scrollPrev, scrollNext, scrollTo, canScrollPrev, canScrollNext, selectedIndex, snapCount.

CarouselPrevious, CarouselNext

Render Button and accept all of its props. They are disabled when there is nothing to scroll to.

PropTypeDefaultDescription
variantButtonVariant'outline'
sizeButtonSize'iconSm'
styleStyleXStyles—StyleX styles merged last — always win over the component’s own styles.

CarouselDots

One button per scroll snap, labelled “Go to slide N”; the active dot has aria-current="true". Each dot is an 8px mark inside a 24px pointer target. The row reserves its height so it does not shift the page when Embla reports the snap count on mount. Accepts all native div props.

PropTypeDefaultDescription
styleStyleXStyles—StyleX styles merged last — always win over the component’s own styles.

Slide size and spacing

CarouselItem defaults to flexBasis: 100% (one slide per view); pass a smaller flexBasis through style for more. The gap comes from a negative marginLeft (horizontal) / marginTop (vertical) on CarouselContent matched by paddingLeft / paddingTop on each CarouselItem — override both with the same value to change it. CarouselContent’s style and other props land on the inner slide track, which is also where a vertical carousel gets its fixed height.

Styling

Carousel, CarouselContent, CarouselItem, CarouselPrevious, CarouselNext, CarouselDots accept style (StyleXStyles, merged last so caller overrides always win) plus all native props of the element they render.

Was this page helpful?