Components
- Accordion
- Alert
- Alert Dialog
- Aspect Ratio
- Attachment
- Avatar
- Badge
- Breadcrumb
- Bubble
- Button
- Button Group
- Calendar
- Card
- Carousel
- Chart
- Checkbox
- Collapsible
- Combobox
- Command
- Context Menu
- Date Picker
- Dialog
- Drawer
- Dropdown Menu
- Empty
- Field
- Hover Card
- Input
- Input OTP
- Input Group
- Item
- Kbd
- Label
- Marker
- Menubar
- Message
- Message Scroller
- Native Select
- Navigation Menu
- Pagination
- Popover
- Progress
- Radio Group
- Resizable
- Scroll Area
- Select
- Separator
- Sheet
- Sidebar
- Skeleton
- Slider
- Sonner
- Spinner
- Switch
- Table
- Tabs
- Textarea
- Toggle
- Toggle Group
- Tooltip
- New Component
Elements
Charts
Icons
External dependency — Carousel requires embla-carousel-react@^8.0.0. Install it before adding this component.
pnpm add embla-carousel-react1
2
3
4
5
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@/components/ui/carousel";
export const CarouselDemo = () => (
<Carousel className="w-full max-w-xs">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}><div className="flex aspect-square items-center justify-center rounded-lg border bg-card p-6"><span className="text-4xl font-semibold">{index + 1}</span></div></CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);Sizes
Control how many items are visible at once using basis-* on CarouselItem.
// 1/3 width — 3 items visible at a time
<Carousel className="w-full max-w-sm">
<CarouselContent>
{items.map((item) => (
<CarouselItem key={item} className="basis-1/3">
...
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Spacing
Add pl-* to CarouselItem to control gap between slides. The default is pl-4.
<CarouselItem className="pl-2 md:basis-1/2">
...
</CarouselItem>Vertical
Set orientation="vertical" for a top-to-bottom scroll direction.
<Carousel orientation="vertical" className="w-full max-w-xs">
<CarouselContent className="h-64">
{items.map((item) => (
<CarouselItem key={item}>...</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>With API
Access the Embla API via setApi to control the carousel programmatically or read state.
"use client";
import { useEffect, useState } from "react";
import type { CarouselApi } from "@/components/ui/carousel";
import { Carousel, CarouselContent, CarouselItem } from "@/components/ui/carousel";
export function CarouselWithApi() {
const [api, setApi] = useState<CarouselApi>();
const [current, setCurrent] = useState(0);
const [count, setCount] = useState(0);
useEffect(() => {
if (!api) return;
setCount(api.scrollSnapList().length);
setCurrent(api.selectedScrollSnap() + 1);
api.on("select", () => setCurrent(api.selectedScrollSnap() + 1));
}, [api]);
return (
<div>
<Carousel setApi={setApi}>
<CarouselContent>
{Array.from({ length: 5 }).map((_, i) => (
<CarouselItem key={i}>Slide {i + 1}</CarouselItem>
))}
</CarouselContent>
</Carousel>
<p className="text-center text-sm text-muted-foreground mt-2">
{current} of {count}
</p>
</div>
);
}Autoplay
Use embla-carousel-autoplay plugin for automatic slide advancement.
pnpm add embla-carousel-autoplayimport Autoplay from "embla-carousel-autoplay";
<Carousel plugins={[Autoplay({ delay: 3000 })]}>
...
</Carousel>Props
Carousel
| Prop | Type | Default | Description |
|---|---|---|---|
orientation | "horizontal" | "vertical" | "horizontal" | Scroll direction |
opts | EmblaOptionsType | — | Embla carousel options |
plugins | EmblaPluginType[] | — | Embla plugins (e.g. autoplay) |
setApi | (api: CarouselApi) => void | — | Callback to access the Embla API |
className | string | — | Additional CSS classes |
CarouselItem
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Use basis-* to set slide width (e.g. basis-1/2) |
CarouselPrevious / CarouselNext
| Prop | Type | Default | Description |
|---|---|---|---|
className | string | — | Override position or size |
Usage
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
<Carousel className="w-full max-w-xs">
<CarouselContent>
{items.map((item) => (
<CarouselItem key={item.id}>
<img src={item.src} alt={item.alt} />
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>Specs
# Mint — Carousel Specs
## Component
- name: carousel
- file: registry/mint/carousel.tsx
- installs to: components/ui/carousel.tsx
- dependencies: embla-carousel-react, embla-carousel, lucide-react
- registryDependencies: button
## What is it
A swipeable, keyboard-navigable slideshow built on Embla Carousel. Use it for
images, cards, testimonials, or any repeating sequence of content. Touch drag
and mouse drag are enabled by default; navigation arrows are rendered outside
the slide track as outline icon buttons. Do not use when content must be
simultaneously visible and compared — prefer a grid or table instead.
## Visual identity
Navigation buttons inherit the outline variant from Button: transparent fill,
a soft glassy green-tinted border, rounded-full shape. The slide track itself
is unstyled, leaving visual character entirely to the content placed inside
each CarouselItem.
## Design tokens
--border: oklch(0.88 0.04 155 / 60%) / oklch(0.32 0.05 155 / 40%) — nav button border (outline variant)
--input: oklch(0.88 0.04 155 / 60%) / oklch(0.32 0.05 155 / 40%) — nav button border on hover (outline variant)
--background: oklch(1 0 0) / oklch(0.145 0 0) — nav button fill (outline variant)
--foreground: oklch(0.145 0 0) / oklch(0.985 0 0) — nav button icon colour
--accent: oklch(0.92 0.05 155) / oklch(0.26 0.05 155) — nav button hover background
--accent-foreground: oklch(0.32 0.1 155) / oklch(0.88 0.06 155) — nav button hover icon colour
--ring: oklch(0.5 0.14 155) / oklch(0.62 0.15 155) — nav button focus ring
## Sub-components
- Carousel → root context provider; sets role="region" aria-roledescription="carousel"; handles keyboard and Embla init
- CarouselContent → overflow-hidden viewport + flex track; negative margin offsets item padding
- CarouselItem → individual slide; role="group" aria-roledescription="slide"; basis-full by default
- CarouselPrevious → prev arrow button; absolute-positioned outside left/top edge; disabled when at start
- CarouselNext → next arrow button; absolute-positioned outside right/bottom edge; disabled when at end
## External dependencies
- embla-carousel-react: core carousel engine (scroll physics, API, plugins)
- embla-carousel: TypeScript types (EmblaCarouselType, EmblaOptionsType)
## States
- disabled: nav buttons get opacity-50 pointer-events-none when canScrollPrev / canScrollNext is false
- focus-visible: 3px ring in --ring colour on nav buttons (inherited from buttonVariants outline)
- hover: nav buttons show --accent background tint (inherited from buttonVariants outline)
## Conventions
- Wrap Carousel in a container with max-w-* to constrain visible width
- Nav buttons position at -left-12 / -right-12 (horizontal) or -top-12 / -bottom-12 (vertical) — add px-12 to the parent to avoid clipping
- Control slide width via basis-* on CarouselItem (e.g. basis-1/2 for two-up, basis-1/3 for three-up)
- Control inter-slide gap via pl-* on CarouselItem (default pl-4); match with -ml-* on CarouselContent
- For vertical carousels, set a fixed height on CarouselContent (e.g. h-64) to define the viewport
- Access the Embla API programmatically via the setApi prop to build dot indicators or auto-advance logic
- Add plugins via the plugins prop — e.g. Autoplay from embla-carousel-autoplay
- Do not place interactive elements inside CarouselItem that conflict with the swipe gesture (e.g. horizontal sliders)
- Sub-components expose data-slot="carousel", "carousel-content", "carousel-item", "carousel-previous", "carousel-next" for CSS targetingInstallation
$ npx shadcn add \https://gin-design.vercel.app/carousel