carousel

A carousel with motion and swipe built using Embla.

  • 1
  • 2
  • 3
  • 4
  • 5
  • About

    The carousel component is built using the Embla Carousel library.

    Installation


    npx @gentleduck/cli add carousel

    npx @gentleduck/cli add carousel

    Usage

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

    Examples

    Sizes

    To set the size of the items, you can use the basis utility class on the <CarouselItem />.

  • 1
  • 2
  • 3
  • 4
  • 5
  • <Carousel>
      <CarouselContent className="-ml-4">
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>
    <Carousel>
      <CarouselContent className="-ml-4">
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>
    <Carousel>
      <CarouselContent className="-ml-2 md:-ml-4">
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>
    <Carousel>
      <CarouselContent className="-ml-2 md:-ml-4">
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>

    Spacing

    To set the spacing between the items, we use a pl-[VALUE] utility on the <CarouselItem /> and a negative -ml-[VALUE] on the <CarouselContent />.

  • 1
  • 2
  • 3
  • 4
  • 5
  • <Carousel>
      <CarouselContent className="-ml-4">
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>
    <Carousel>
      <CarouselContent className="-ml-4">
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
        <CarouselItem className="pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>
    <Carousel>
      <CarouselContent className="-ml-2 md:-ml-4">
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>
    <Carousel>
      <CarouselContent className="-ml-2 md:-ml-4">
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
        <CarouselItem className="pl-2 md:pl-4">...</CarouselItem>
      </CarouselContent>
    </Carousel>

    Orientation

    Use the orientation prop to set the orientation of the carousel.

  • 1
  • 2
  • 3
  • 4
  • 5
  • <Carousel orientation="vertical | horizontal">
      <CarouselContent>
        <CarouselItem>...</CarouselItem>
        <CarouselItem>...</CarouselItem>
        <CarouselItem>...</CarouselItem>
      </CarouselContent>
    </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>
    <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.

  • 1
  • 2
  • 3
  • 4
  • 5
  • Slide 0 of 0
    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>
      )
    }
    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>
      )
    }
    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>
      )
    }
    import Autoplay from "embla-carousel-autoplay"
     
    export function Example() {
      return (
        <Carousel
          plugins={[
            Autoplay({
              delay: 2000,
            }),
          ]}
        >
          // ...
        </Carousel>
      )
    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • See the Embla Carousel docs for more information on using plugins.

    API Reference

    • opts (CarouselOptions, optional): Configuration options passed to embla-carousel.
    • plugins (CarouselPlugin, optional): Array of Embla plugins.
    • orientation ('horizontal' | 'vertical', default: 'horizontal'): Carousel axis direction.
    • setApi ((api: CarouselApi) => void, optional): Callback to expose the Embla API.
    • ...props (React.HTMLProps<HTMLDivElement>): All other standard <div> props.

    Behavior:

    • Provides context (CarouselContext) for child components.
    • Handles keyboard navigation (Left/Right arrows).
    • Tracks whether previous/next scroll is possible.

    CarouselContent

    • props (React.HTMLProps<HTMLDivElement>): Standard <div> props.

    Behavior:

    • Wraps slides in an Embla scroll container.
    • Adjusts layout based on orientation (horizontal or vertical).

    CarouselItem

    • props (React.HTMLProps<HTMLDivElement>): Standard <div> props.

    Behavior:

    • Represents a single slide with role="group" and aria-roledescription="slide".
    • Uses spacing (pl-4 or pt-4) depending on orientation.

    CarouselPrevious

    • variant (string, default: 'outline'): Variant prop for the Button component.
    • size (string, default: 'icon'): Size prop for the Button component.
    • ...props (React.ComponentProps<typeof Button>): All other Button props.

    Behavior:

    • Scrolls to the previous slide when clicked.
    • Disabled when canScrollPrev is false.
    • Positioned absolutely relative to carousel orientation.

    CarouselNext

    • variant (string, default: 'outline'): Variant prop for the Button component.
    • size (string, default: 'icon'): Size prop for the Button component.
    • ...props (React.ComponentProps<typeof Button>): All other Button props.

    Behavior:

    • Scrolls to the next slide when clicked.
    • Disabled when canScrollNext is false.
    • Positioned absolutely relative to carousel orientation.

    Types

    type CarouselApi = UseEmblaCarouselType[1]
    type CarouselOptions = Parameters<typeof useEmblaCarousel>[0]
    type CarouselPlugin = Parameters<typeof useEmblaCarousel>[1]
     
    type CarouselProps = {
      opts?: CarouselOptions
      plugins?: CarouselPlugin
      orientation?: 'horizontal' | 'vertical'
      setApi?: (api: CarouselApi) => void
    }
     
    type CarouselContextProps = {
      carouselRef: ReturnType<typeof useEmblaCarousel>[0]
      api: CarouselApi
      scrollPrev: () => void
      scrollNext: () => void
      canScrollPrev: boolean
      canScrollNext: boolean
    } & CarouselProps
    type CarouselApi = UseEmblaCarouselType[1]
    type CarouselOptions = Parameters<typeof useEmblaCarousel>[0]
    type CarouselPlugin = Parameters<typeof useEmblaCarousel>[1]
     
    type CarouselProps = {
      opts?: CarouselOptions
      plugins?: CarouselPlugin
      orientation?: 'horizontal' | 'vertical'
      setApi?: (api: CarouselApi) => void
    }
     
    type CarouselContextProps = {
      carouselRef: ReturnType<typeof useEmblaCarousel>[0]
      api: CarouselApi
      scrollPrev: () => void
      scrollNext: () => void
      canScrollPrev: boolean
      canScrollNext: boolean
    } & CarouselProps