Carousel

A slider to display multiple items in a scrollable view.

1
2
3
4
5
carousel-demo.tsx
import { AspectRatio } from "@/components/ui/aspect-ratio"
import {
	Carousel,
	CarouselContent,
	CarouselItem,
	CarouselNext,
	CarouselPrevious,
} from "@/components/ui/carousel"
 
const slides = [1, 2, 3, 4, 5]
 
export function CarouselDemo() {
	return (
		<div className="w-60 sm:w-80 lg:w-96">
			<Carousel>
				<CarouselContent>
					{slides.map((slide) => (
						<CarouselItem key={slide}>
							<AspectRatio
								ratio={16 / 9}
								className="bg-background rounded-lg border"
							>
								<div className="text-foreground flex size-full items-center justify-center text-xl font-semibold">
									{slide}
								</div>
							</AspectRatio>
						</CarouselItem>
					))}
				</CarouselContent>
				<CarouselPrevious />
				<CarouselNext />
			</Carousel>
		</div>
	)
}

Installation

npx shadcn@latest add https://9ui.dev/r/carousel.json

Usage

Imports
import {
	Carousel,
	CarouselContent,
	CarouselItem,
	CarouselNext,
	CarouselPrevious,
} from "@/components/ui/carousel"
Anatomy
<Carousel>
	<CarouselContent>
		<CarouselItem />
	</CarouselContent>
	<CarouselPrevious />
	<CarouselNext />
</Carousel>

Examples

Vertical

1
2
3
4
5
carousel-vertical.tsx
import { AspectRatio } from "@/components/ui/aspect-ratio"
import {
	Carousel,
	CarouselContent,
	CarouselItem,
	CarouselNext,
	CarouselPrevious,
} from "@/components/ui/carousel"
 
const slides = [1, 2, 3, 4, 5]
 
export function CarouselVertical() {
	return (
		<div className="w-60 py-8 sm:w-80 lg:w-96">
			<Carousel orientation="vertical" opts={{ loop: true }}>
				<CarouselContent className="aspect-video h-[-webkit-fill-available] w-full p-4">
					{slides.map((slide) => (
						<CarouselItem key={slide} className="basis-full">
							<AspectRatio
								ratio={16 / 9}
								className="bg-background rounded-lg border"
							>
								<div className="text-foreground flex size-full items-center justify-center font-medium">
									{slide}
								</div>
							</AspectRatio>
						</CarouselItem>
					))}
				</CarouselContent>
				<CarouselPrevious />
				<CarouselNext />
			</Carousel>
		</div>
	)
}

Multiple

1
2
3
4
5
carousel-multiple.tsx
import { AspectRatio } from "@/components/ui/aspect-ratio"
import {
	Carousel,
	CarouselContent,
	CarouselItem,
	CarouselNext,
	CarouselPrevious,
} from "@/components/ui/carousel"
 
const slides = [1, 2, 3, 4, 5]
 
export function CarouselMultiple() {
	return (
		<div className="w-60 sm:w-80 lg:w-96">
			<Carousel>
				<CarouselContent>
					{slides.map((slide) => (
						<CarouselItem key={slide} className="basis-1/2">
							<AspectRatio
								ratio={16 / 10}
								className="bg-background rounded-lg border"
							>
								<div className="text-foreground flex size-full items-center justify-center text-xl font-semibold">
									{slide}
								</div>
							</AspectRatio>
						</CarouselItem>
					))}
				</CarouselContent>
				<CarouselPrevious />
				<CarouselNext />
			</Carousel>
		</div>
	)
}

Looped

1
2
3
4
5
carousel-looped.tsx
import { AspectRatio } from "@/components/ui/aspect-ratio"
import {
	Carousel,
	CarouselContent,
	CarouselItem,
	CarouselNext,
	CarouselPrevious,
} from "@/components/ui/carousel"
 
const slides = [1, 2, 3, 4, 5]
 
export function CarouselLooped() {
	return (
		<div className="w-60 sm:w-80 lg:w-96">
			<Carousel opts={{ loop: true }}>
				<CarouselContent>
					{slides.map((slide) => (
						<CarouselItem key={slide}>
							<AspectRatio
								ratio={16 / 9}
								className="bg-background rounded-lg border"
							>
								<div className="text-foreground flex size-full items-center justify-center text-xl font-semibold">
									{slide}
								</div>
							</AspectRatio>
						</CarouselItem>
					))}
				</CarouselContent>
				<CarouselPrevious />
				<CarouselNext />
			</Carousel>
		</div>
	)
}

Thumbnail

Carousel slide
Carousel slide
Carousel slide
Carousel slide
Carousel slide
carousel-thumbnail.tsx
import { useState } from "react"
import Image from "next/image"
 
import { AspectRatio } from "@/components/ui/aspect-ratio"
import {
	Carousel,
	CarouselApi,
	CarouselContent,
	CarouselItem,
} from "@/components/ui/carousel"
 
import { cn } from "@/lib/utils"
 
const slides = [
	"https://images.pexels.com/photos/1616403/pexels-photo-1616403.jpeg?auto=compress&cs=tinysrgb&w=450&h=800&dpr=2",
	"https://images.pexels.com/photos/1293120/pexels-photo-1293120.jpeg?auto=compress&cs=tinysrgb&w=450&h=800&dpr=2",
	"https://images.pexels.com/photos/1103970/pexels-photo-1103970.jpeg?auto=compress&cs=tinysrgb&w=450&h=800&dpr=2",
	"https://images.pexels.com/photos/2011824/pexels-photo-2011824.jpeg?auto=compress&cs=tinysrgb&w=450&h=800&dpr=2",
	"https://images.pexels.com/photos/2471235/pexels-photo-2471235.jpeg?auto=compress&cs=tinysrgb&w=450&h=800&dpr=2",
]
 
export function CarouselThumbnail() {
	const [api, setApi] = useState<CarouselApi>()
	const [selectedIndex, setSelectedIndex] = useState(0)
 
	api?.on("select", () => {
		setSelectedIndex(api?.selectedScrollSnap() ?? 0)
	})
 
	return (
		<div className="w-60">
			<Carousel setApi={setApi}>
				<CarouselContent>
					{slides.map((slide) => (
						<CarouselItem key={slide}>
							<AspectRatio
								ratio={16 / 9}
								className="bg-background rounded-lg border"
							>
								<Image
									src={slide}
									alt="Carousel slide"
									fill
									className="rounded-lg object-cover"
								/>
							</AspectRatio>
						</CarouselItem>
					))}
				</CarouselContent>
				<div className="mt-2 flex w-full items-center justify-between">
					{slides.map((slide, index) => (
						<button
							key={slide}
							className="relative size-10"
							onClick={() => api?.scrollTo(index)}
						>
							<Image
								src={slide}
								alt="Carousel slide"
								fill
								className={cn(
									"rounded-md object-cover opacity-60 transition-opacity duration-200 hover:opacity-100",
									selectedIndex === index && "opacity-100"
								)}
							/>
						</button>
					))}
				</div>
			</Carousel>
		</div>
	)
}