View fulldotdev/ui on GitHub

Carousel

A carousel with motion, swipe and responsive slides built using Embla.

Installation

npx shadcn@latest add @fulldev/carousel

Usage

import {
  Carousel,
  CarouselContent,
  CarouselItem,
  CarouselNext,
  CarouselPrevious,
} from "@/components/ui/carousel"
<Carousel aria-label="Featured slides">
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

The carousel runs the vanilla Embla Carousel engine in a client script and needs no React. CarouselContent renders the overflow viewport and an inner track; its classes and attributes apply to the track.

Carousel renders a section with aria-roledescription="carousel". Give it an aria-label or aria-labelledby so it becomes a named region, as the carousel pattern recommends; without a name it gets role="group".

Composition

Carousel
├── CarouselContent
│   ├── CarouselItem
│   └── CarouselItem
├── CarouselPrevious
└── CarouselNext

Sizes

Set the size of the items with a basis-* class on CarouselItem, such as basis-1/3 or md:basis-1/2 lg:basis-1/3.

Spacing

Use pl-[VALUE] on CarouselItem and a matching -ml-[VALUE] on CarouselContent.

Orientation

Use the orientation prop to set the scroll axis. Give a vertical CarouselContent an explicit height.

Options

Pass serializable Embla options through opts, such as align, loop, startIndex, watchDrag, slidesToScroll, direction and breakpoints. orientation sets axis. Previous, next and keyboard navigation (arrow keys, Home, End) jump instead of animate when the user prefers reduced motion.

<Carousel opts={{ align: "start", loop: true }}>
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
</Carousel>

API

Listen for carousel:init on the root to receive the Embla API. For code that runs after initialization, the root’s __fulldevCarouselApi property holds the current API.

Events

carousel:select fires after each selection and reinitialization. Both events expose detail.api, canScrollPrev, canScrollNext, selectedScrollSnap and scrollSnapList.

import type { CarouselApi } from "@/components/ui/carousel"

const carousel = document.querySelector('[data-slot="carousel"]')
carousel?.addEventListener("carousel:select", (event) => {
  const { api } = (event as CustomEvent<{ api: CarouselApi }>).detail
  // Do something on select.
})

Instances are destroyed before Astro page swaps and recreated on page load.

Plugins

Plugin instances are functions, so they cannot be passed as Astro props. This example adds autoplay with a short script on the Embla API from carousel:init: it advances every 2 seconds and stops on hover, drag or focus.

RTL

Set dir on Carousel and the matching direction option in opts so the carousel scrolls in the reading direction.

API Reference

Source: src/components/ui/carousel

PropTypeDefault
optsCarouselOptionsundefined
orientation"horizontal" | "vertical""horizontal"

CarouselPrevious and CarouselNext accept the Button variant and size props (default "outline" and "icon-sm") and are disabled automatically when the carousel cannot scroll that way, unless you pass disabled. The root exposes data-orientation, data-can-scroll-prev, data-can-scroll-next and data-selected-scroll-snap. CarouselApi, CarouselOptions and CarouselPlugin re-export Embla’s types.

Migration from 0.13

Reinstall the registry component so markup and runtime stay together.

0.13Embla carousel
defaultIndex={1}opts={{ startIndex: 1 }}
loopopts={{ loop: true }}
dragEnabled by default; disable with opts={{ watchDrag: false }}
CarouselControllerCarouselApi
carousel:changecarousel:select
carousel:setCall methods on detail.api from carousel:init
Content classes style the viewportContent classes style the inner track