import { styled } from "styled-system/jsx";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
export default function CarouselDemo() {
return (
<Carousel css={{ w: "full", maxW: "xs" }}>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<styled.div css={{ p: "1" }}>
<Card>
<CardContent
css={{
display: "flex",
aspectRatio: "square",
alignItems: "center",
justifyContent: "center",
p: "6",
}}
>
<styled.span css={{ textStyle: "4xl", fontWeight: "semibold" }}>
{index + 1}
</styled.span>
</CardContent>
</Card>
</styled.div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
About
The carousel component is built using the Embla Carousel library.
Installation
npx nore-ui-cli@latest add carouselUsage
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
<Carousel>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
Examples
Size
To set the size of the items, you can use the flexBasis on the <CarouselItem />.
import { styled } from "styled-system/jsx";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
export default function CarouselSize() {
return (
<Carousel
opts={{
align: "start",
}}
css={{ w: "full", maxW: "sm" }}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} css={{ md: { flexBasis: "1/2" }, lg: { flexBasis: "1/3" } }}>
<styled.div css={{ p: "1" }}>
<Card>
<CardContent
css={{
display: "flex",
aspectRatio: "square",
alignItems: "center",
justifyContent: "center",
p: "6",
}}
>
<styled.span css={{ textStyle: "3xl", fontWeight: "semibold" }}>
{index + 1}
</styled.span>
</CardContent>
</Card>
</styled.div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
// 33% of the carousel width.
<Carousel>
<CarouselContent>
<CarouselItem flexBasis="1/3">...</CarouselItem>
<CarouselItem flexBasis="1/3">...</CarouselItem>
<CarouselItem flexBasis="1/3">...</CarouselItem>
</CarouselContent>
</Carousel>
// 50% on small screens and 33% on larger screens.
<Carousel>
<CarouselContent>
<CarouselItem flexBasis={{ md: "1/2", lg: "1/3" }}>...</CarouselItem>
<CarouselItem flexBasis={{ md: "1/2", lg: "1/3" }}>...</CarouselItem>
<CarouselItem flexBasis={{ md: "1/2", lg: "1/3" }}>...</CarouselItem>
</CarouselContent>
</Carousel>
Spacing
To set the spacing between the items, we use a pl on the and a
negative ml on the <CarouselContent />.
Why: I tried to use the gap property or a grid layout on the <CarouselContent /> but it
required a lot of math and mental effort to get the spacing right. I found pl and negative
ml much easier to use.
You can always adjust this in your own project if you need to.
import { styled } from "styled-system/jsx";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
export default function CarouselSpacing() {
return (
<Carousel css={{ w: "full", maxW: "sm" }}>
<CarouselContent css={{ ml: "-1" }}>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem
key={index}
css={{ pl: "1", md: { flexBasis: "1/2" }, lg: { flexBasis: "1/3" } }}
>
<styled.div css={{ p: "1" }}>
<Card>
<CardContent
css={{
display: "flex",
aspectRatio: "square",
alignItems: "center",
justifyContent: "center",
p: "6",
}}
>
<styled.span css={{ textStyle: "2xl", fontWeight: "semibold" }}>
{index + 1}
</styled.span>
</CardContent>
</Card>
</styled.div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
<Carousel>
<CarouselContent ml="-4">
<CarouselItem pl="4">...</CarouselItem>
<CarouselItem pl="4">...</CarouselItem>
<CarouselItem pl="4">...</CarouselItem>
</CarouselContent>
</Carousel>
<Carousel>
<CarouselContent ml={{ base: "-2", md: "-4" }}>
<CarouselItem pl={{ base: "2", md: "4" }}>...</CarouselItem>
<CarouselItem pl={{ base: "2", md: "4" }}>...</CarouselItem>
<CarouselItem pl={{ base: "2", md: "4" }}>...</CarouselItem>
</CarouselContent>
</Carousel>
Orientation
Use the orientation prop to set the orientation of the carousel.
import { styled } from "styled-system/jsx";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
export default function CarouselOrientation() {
return (
<Carousel
opts={{
align: "start",
}}
orientation="vertical"
css={{ w: "full", maxW: "xs", my: "24" }}
>
<CarouselContent css={{ mt: "-1", h: "200px" }}>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index} css={{ pt: "1", md: { flexBasis: "1/2" } }}>
<styled.div css={{ p: "1" }}>
<Card>
<CardContent
css={{ display: "flex", alignItems: "center", justifyContent: "center", p: "6" }}
>
<styled.span css={{ textStyle: "3xl", fontWeight: "semibold" }}>
{index + 1}
</styled.span>
</CardContent>
</Card>
</styled.div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
<Carousel orientation="vertical | horizontal">
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
Options
You can pass options to the carousel using the opts prop. See the Embla Carousel docs
for more information.
<Carousel
opts={{
align: "start",
loop: true,
}}
>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
API
Use a state and the setApi props to get an instance of the carousel API.
"use client";
import * as React from "react";
import { styled } from "styled-system/jsx";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
type CarouselApi,
} from "@/components/ui/carousel";
export default function CarouselApiDemo() {
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 (
<styled.div css={{ mx: "auto", maxW: "xs" }}>
<Carousel setApi={setApi} css={{ w: "full", maxW: "xs" }}>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<Card>
<CardContent
css={{
display: "flex",
aspectRatio: "square",
alignItems: "center",
justifyContent: "center",
p: "6",
}}
>
<styled.span css={{ textStyle: "4xl", fontWeight: "semibold" }}>
{index + 1}
</styled.span>
</CardContent>
</Card>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
<styled.div css={{ py: "2", textAlign: "center", textStyle: "sm", color: "muted.fg" }}>
Slide {current} of {count}
</styled.div>
</styled.div>
);
}
import { type CarouselApi } from "@/components/ui/carousel";
export function Example() {
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 (
<Carousel setApi={setApi}>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
);
}
Events
You can listen to events using the api instance from setApi.
import { type CarouselApi } from "@/components/ui/carousel";
export function Example() {
const [api, setApi] = React.useState<CarouselApi>();
React.useEffect(() => {
if (!api) {
return;
}
api.on("select", () => {
// Do something on select.
});
}, [api]);
return (
<Carousel setApi={setApi}>
<CarouselContent>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
<CarouselItem>...</CarouselItem>
</CarouselContent>
</Carousel>
);
}
See the Embla Carousel docs for more information on using events.
Plugins
You can use the plugins prop to add plugins to the carousel.
import Autoplay from "embla-carousel-autoplay";
export function Example() {
return (
<Carousel
plugins={[
Autoplay({
delay: 2000,
}),
]}
>
// ...
</Carousel>
);
}
"use client";
import * as React from "react";
import Autoplay from "embla-carousel-autoplay";
import { styled } from "styled-system/jsx";
import { Card, CardContent } from "@/components/ui/card";
import {
Carousel,
CarouselContent,
CarouselItem,
CarouselNext,
CarouselPrevious,
} from "@/components/ui/carousel";
export default function CarouselPlugin() {
const plugin = React.useRef(Autoplay({ delay: 2000, stopOnInteraction: true }));
return (
<Carousel
plugins={[plugin.current]}
onMouseEnter={plugin.current.stop}
onMouseLeave={plugin.current.reset}
css={{ w: "full", maxW: "xs" }}
>
<CarouselContent>
{Array.from({ length: 5 }).map((_, index) => (
<CarouselItem key={index}>
<styled.div css={{ p: "1" }}>
<Card>
<CardContent
css={{
display: "flex",
aspectRatio: "square",
alignItems: "center",
justifyContent: "center",
p: "6",
}}
>
<styled.span css={{ textStyle: "4xl", fontWeight: "semibold" }}>
{index + 1}
</styled.span>
</CardContent>
</Card>
</styled.div>
</CarouselItem>
))}
</CarouselContent>
<CarouselPrevious />
<CarouselNext />
</Carousel>
);
}
See the Embla Carousel docs for more information on using plugins.