Carousel
A carousel with motion and swipe built using Embla.
When to Use
Use Carousel when you need to:
- Display a sequence of images, cards, or content in a limited space
- Create product galleries, testimonial sliders, or feature tours
- Show multiple items that can be browsed one-at-a-time or in groups
- Present media-rich content with swipe and drag interaction on touch devices
- Build hero banners or promotional content rotators
When Not to Use
- Long scrollable lists - use Scroll Area instead
- Tabbed content where all sections are equally important - use Tabs
- Expandable/collapsible content sections - use Accordion
- Critical content that must be seen by all users - carousels hide content behind interaction
Default
Basic carousel with numbered slides and previous/next navigation.
With Images
Full-width image slides using AspectRatio for consistent 16:9 sizing.
Image Cards
Multiple image cards with titles displayed in a multi-item layout using basis classes.
Sizes
Control how many items are visible at once using Tailwind basis utility classes on CarouselItem.
Spacing
Customize gap between slides using pl-* on items and -ml-* on content.
Vertical
Vertical orientation with up/down scroll navigation. Set a fixed height on CarouselContent.
UX & Design Guidelines
Visual Hierarchy
Keep carousel content visually consistent across slides. Use bg-card with border-border for slide containers. Navigation buttons use variant="outline" by default to remain unobtrusive while still discoverable. Add dot indicators or slide counts for orientation context.
Spacing & Layout
The default gap between items is pl-4 (1rem). For tighter layouts, override with pl-1 on items and -ml-1 on CarouselContent. Navigation buttons are positioned -left-12 and -right-12, so ensure adequate horizontal padding on the parent container (at least px-12).
Responsive Behavior
Use responsive basis classes like md:basis-1/2 lg:basis-1/3 on CarouselItem to show more slides on larger screens. On mobile, default to full-width slides (basis-full). Touch and swipe interactions work automatically via Embla. Consider hiding navigation arrows on touch devices where swipe is intuitive.
Content & Media
Always use AspectRatio for image carousels to prevent layout shift. Use object-cover with fill on next/image for responsive images. Limit carousel to 8-10 slides maximum; beyond that, users rarely navigate to later items. For auto-playing carousels, provide a visible pause control and stop on user interaction.
Accessibility
Keyboard Navigation
- ArrowLeft — Scroll to the previous slide
- ArrowRight — Scroll to the next slide
- Tab — Move focus to navigation buttons and interactive content within slides
- Enter or Space — Activate the focused navigation button
Screen Reader Support
- The carousel container has
role="region"andaria-roledescription="carousel" - Each slide has
role="group"andaria-roledescription="slide" - Navigation buttons include
sr-onlylabels ("Previous slide" and "Next slide") - Disabled state is announced when first/last slide is reached (non-loop mode)
Focus Management
Arrow key events are captured at the container level via onKeyDownCapture, ensuring keyboard navigation works when any element within the carousel is focused. Navigation buttons use visible focus rings with focus-visible:ring-[3px] inherited from the Button component.
Touch & Pointer
Embla provides native drag-to-scroll on both touch and pointer devices. Navigation buttons are 32px (size-8) round targets, meeting minimum touch target recommendations. Buttons are automatically disabled at scroll boundaries to prevent no-op interactions.
ARIA Attributes
{/* Container with carousel role */}
<Carousel aria-label="Product gallery">
<CarouselContent>
{/* Each slide has role="group" and aria-roledescription="slide" */}
<CarouselItem aria-label="1 of 5">
...
</CarouselItem>
</CarouselContent>
{/* Navigation buttons have built-in sr-only labels */}
<CarouselPrevious />
<CarouselNext />
</Carousel>
{/* Auto-playing carousel with pause control */}
<Carousel
plugins={[Autoplay({ delay: 3000, stopOnInteraction: true })]}
aria-label="Featured announcements"
aria-live="off"
>
...
</Carousel>Related Components
API Reference
The Carousel system is composed of five parts. The main container accepts the following props in addition to standard div attributes.
Carousel
Main container with Embla integration and keyboard navigation.
| Prop | Type | Default | Description |
|---|---|---|---|
| opts | EmblaOptionsType | — | Embla carousel options including align, loop, dragFree, and skipSnaps. |
| orientation | "horizontal" | "vertical" | "horizontal" | The scroll axis of the carousel. |
| setApi | (api: EmblaCarouselType) => void | — | Callback to receive the Embla carousel API instance for programmatic control. |
| plugins | EmblaPluginType[] | — | Embla plugins such as Autoplay, AutoScroll, or ClassNames. |
| className | string | — | Additional CSS classes to apply to the carousel container. |
CarouselContent
Scrollable flex container that wraps all slides.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | CSS classes for the scrollable flex container. Use -ml-* to adjust item spacing. |
CarouselItem
Individual slide wrapper with role="group" semantics.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | CSS classes for sizing (basis-1/2, basis-1/3) and spacing (pl-*). |
| children | ReactNode | — | The content of the carousel slide. |
CarouselPrevious / CarouselNext
Absolutely-positioned navigation buttons. Accept all Button props.
| Prop | Type | Default | Description |
|---|---|---|---|
| variant | "default" | "outline" | "ghost" | "secondary" | "outline" | The visual style variant of the navigation button. |
| size | "default" | "sm" | "lg" | "icon" | "icon" | The size of the navigation button. |
| className | string | — | Additional CSS classes for positioning or styling the navigation button. |