Carousel

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

About

The carousel uses the vanilla Embla Carousel engine, following shadcn/ui composition and CSS slide sizing. It needs no React. Other interactive Fulldev UI components continue to use Data Slot.

CarouselContent provides an overflow viewport and an inner track. Classes and HTML attributes passed to CarouselContent apply to the track. Use responsive basis-* classes on items and matching negative track margins / slide padding for spacing. No multiple option is needed.

Installation

Install the component from the @fulldev registry with the shadcn CLI:

npx shadcn@latest add @fulldev/carousel

Manual install:

npm install embla-carousel

Then copy the component files into src/components/ui/carousel and update the imports to match your project structure.

Usage

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

<Carousel>
  <CarouselContent>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
    <CarouselItem>...</CarouselItem>
  </CarouselContent>
  <CarouselPrevious />
  <CarouselNext />
</Carousel>

Composition

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

Options

Pass serializable Embla options through opts: align, loop, startIndex, watchDrag, slidesToScroll and responsive breakpoints are supported. orientation="vertical" sets the scrolling axis; otherwise opts.axis is respected. Dragging and focus scrolling use Embla’s defaults. Previous/next and keyboard navigation respect reduced motion.

Vertical

Give a vertical CarouselContent an explicit height so each item can fill its viewport.

Events and API

Listen for carousel:init to receive the Embla API. carousel:select fires after selection or 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:init", (event) => {
  const { api } = (event as CustomEvent<{ api: CarouselApi }>).detail
  api.scrollTo(2)
})

Register the listener before initialization. For code running later, the root’s __fulldevCarouselApi property holds the current API. CarouselOptions and CarouselPlugin re-export Embla’s corresponding types. Plugin instances are configured through Embla’s API, not serialized Astro props.

Instances are destroyed before Astro page swaps and recreated on page load. Visible links retain normal focus behavior; focusing an offscreen slide lets Embla bring it into view. No custom inert or aria-hidden overrides are added.

Multiple visible slides

Use CSS widths just as in shadcn/ui. This example shows three clickable slides at once, with extra slides reachable through dragging, buttons or keyboard.

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

The former one-full-width-slide restriction no longer applies. Use matching negative margins on content and padding on items for spacing, like shadcn/ui.