Command Palette

Search for a command...

Carousel

A carousel with motion and swipe built using Embla.

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 carousel

Usage

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.

Slide 0 of 0
"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.