Carousel
Embla-powered slide carousel with prev/next controls. Supports horizontal and vertical axes.
Examples
Multiple per view
Show several items at once with basis utilities.
import { Card, CardContent } from "@codefast/ui/card";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
export function CarouselMultiple() {
return (
<Carousel
className="mx-auto max-w-xs sm:max-w-sm"
opts={{
align: "start",
}}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="sm:basis-1/2 lg:basis-1/3">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious className="hidden sm:inline-flex" />
<CarouselNext className="hidden sm:inline-flex" />
</Carousel>
);
}
API
Use a state and the setApi prop to get an instance of the carousel API.
import { Card, CardContent } from "@codefast/ui/card";
import type { CarouselApi } from "@codefast/ui/carousel";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
import * as React from "react";
export function CarouselDApiDemo() {
const [api, setApi] = React.useState<CarouselApi>();
const [current, setCurrent] = React.useState(0);
const [count, setCount] = React.useState(0);
React.useEffect(() => {
if (!api) {
return;
}
setCount(api.scrollSnapList().length);
setCurrent(api.selectedScrollSnap() + 1);
api.on("select", () => {
setCurrent(api.selectedScrollSnap() + 1);
});
}, [api]);
return (
<div className="mx-auto max-w-40 sm:max-w-xs">
<Carousel setApi={setApi} className="w-full max-w-xs">
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<Card className="m-px">
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<div className="py-2 text-center text-sm text-muted-foreground">
Slide {current} of {count}
</div>
</div>
);
}
Orientation
Use the orientation prop to set the orientation of the carousel.
import { Card, CardContent } from "@codefast/ui/card";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
export function CarouselOrientation() {
return (
<Carousel
opts={{
align: "start",
}}
orientation="vertical"
className="w-full max-w-xs"
>
<CarouselContent className="-mt-1 h-67.5">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="basis-1/2 pt-1">
<div className="p-1">
<Card>
<CardContent className="flex items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
Plugins
Add plugins such as Autoplay to the carousel using the plugins prop.
import { Card, CardContent } from "@codefast/ui/card";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
import Autoplay from "embla-carousel-autoplay";
import * as React from "react";
export function CarouselPlugin() {
const plugin = React.useRef(Autoplay({ delay: 2000, stopOnInteraction: true }));
return (
<Carousel
plugins={[plugin.current]}
className="w-full max-w-40 sm:max-w-xs"
onMouseEnter={plugin.current.stop}
onMouseLeave={plugin.current.reset}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
RTL
Right-to-left layout support for languages such as Arabic and Hebrew.
Translations are AI-generated for demonstration and may be imperfect.
import { Card, CardContent } from "@codefast/ui/card";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
import type { Translations } from "#/features/components-catalog/components/detail/language";
import { useTranslation } from "#/features/components-catalog/components/detail/language-context";
const translations: Translations = {
en: {
dir: "ltr",
values: {},
},
ar: {
dir: "rtl",
values: {},
},
he: {
dir: "rtl",
values: {},
},
};
function toArabicNumerals(num: number): string {
const arabicNumerals = ["٠", "١", "٢", "٣", "٤", "٥", "٦", "٧", "٨", "٩"];
return num
.toString()
.split("")
.map((digit) => arabicNumerals[parseInt(digit, 10)])
.join("");
}
export function CarouselRtl() {
const { dir, language } = useTranslation(translations, "ar");
const formatNumber = (num: number): string => {
if (language === "ar") {
return toArabicNumerals(num);
}
return num.toString();
};
return (
<Carousel
dir={dir}
className="w-full max-w-48 sm:max-w-xs"
opts={{
direction: dir,
}}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<div className="p-1">
<Card dir={dir}>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-4xl font-semibold">{formatNumber(index + 1)}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
Sizes
To set the size of the items, you can use the basis utility class on the <CarouselItem />.
import { Card, CardContent } from "@codefast/ui/card";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
export function CarouselSize() {
return (
<Carousel
opts={{
align: "start",
}}
className="w-full max-w-48 sm:max-w-xs md:max-w-sm"
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="basis-1/2 lg:basis-1/3">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-3xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
Spacing
To set the spacing between the items, we use a ps-[VALUE] utility on the <CarouselItem /> and a negative -ms-[VALUE] on the <CarouselContent />.
import { Card, CardContent } from "@codefast/ui/card";
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
export function CarouselSpacing() {
return (
<Carousel className="w-full max-w-48 sm:max-w-xs md:max-w-sm">
<CarouselContent className="-ms-1">
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} className="basis-1/2 ps-1 lg:basis-1/3">
<div className="p-1">
<Card>
<CardContent className="flex aspect-square items-center justify-center p-6">
<span className="text-2xl font-semibold">{index + 1}</span>
</CardContent>
</Card>
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
Usage
The minimal import and composition — see Examples below for styled, real-world variants.
import { Carousel, CarouselContent, CarouselItem, CarouselNext, CarouselPrevious } from "@codefast/ui/carousel";
export function CarouselUsage() {
return (
<Carousel className="w-full max-w-xs">
<CarouselContent>
{Array.from({ length: 5 }, (_, index) => (
<CarouselItem key={index}>
<div className="flex aspect-square items-center justify-center rounded-xl border text-4xl font-semibold">
{index + 1}
</div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
Anatomy
How the parts nest — every slot the component exposes, in composition order.
Features
- Built on Embla; setApi exposes the live Embla instance to read the current index or call scrollTo() imperatively.
- Arrow-key navigation follows reading direction — in RTL, Left advances instead of Right.
- CarouselPrevious/CarouselNext disable themselves automatically at the ends of an unlooped carousel.
API reference
Props for each part of the component. All native element props are also forwarded.
Carousel
Embla-powered. Owns the viewport and exposes the underlying api.
orientation"horizontal" | "vertical"Scroll axis.
Default
"horizontal"optsEmblaOptionsTypeEmbla options, e.g. { align: "start", loop: true }.
setApi(api) => voidReceive the Embla api to read the index or call scrollTo().
pluginsEmblaPluginType[]Embla plugins such as Autoplay.
CarouselItem
classNamestringSet basis-1/2, basis-1/3… to show multiple items per view.
Accessibility
Built to be keyboard-navigable and screen-reader friendly out of the box.
| Key | Function |
|---|---|
| Tab | Focuses the carousel region, then the prev/next buttons. |
| Arrow+Left | Scrolls to the previous slide. |
| Arrow+Right | Scrolls to the next slide. |
- The region is labelled as a carousel; prev/next buttons disable at the ends.
- Provide a text counter or labelled dots so position isn’t conveyed only visually.
- Avoid autoplay for essential content, or pause it on hover/focus.
Guidelines
Conventions that keep usage consistent across an app.
Do
- Show position with a counter or dot indicators.
- Use basis utilities on items to reveal a peek of the next slide.
Don’t
- Don’t hide critical content inside an autoplaying carousel.
- Don’t remove the prev/next controls on non-touch devices.
Explore further
Ready to integrate?
Follow the Getting Started guide to install @codefast/ui, or browse the full component gallery.