Sabil UI

Search documentation

Search for a command to run...

Components

Carousel

A carousel with motion and swipe built using Embla.

1
2
3
4
5

Installation#

  1. Install the dependencies

    npm install cn embla-carousel-react lucide-react
  2. Copy the source into your project

    components/ui/carousel.tsx
    "use client"import * as React from "react"import { cn } from "cn"import useEmblaCarousel, {  type UseEmblaCarouselType,} from "embla-carousel-react"import { Button } from "@/components/ui/button"import { ChevronLeftIcon, ChevronRightIcon } from "lucide-react"type CarouselApi = UseEmblaCarouselType[1]type UseCarouselParameters = Parameters<typeof useEmblaCarousel>type CarouselOptions = UseCarouselParameters[0]type CarouselPlugin = UseCarouselParameters[1]type CarouselProps = {  opts?: CarouselOptions  plugins?: CarouselPlugin  orientation?: "horizontal" | "vertical"  setApi?: (api: CarouselApi) => void}type CarouselContextProps = {  carouselRef: ReturnType<typeof useEmblaCarousel>[0]  api: ReturnType<typeof useEmblaCarousel>[1]  scrollPrev: () => void  scrollNext: () => void  canScrollPrev: boolean  canScrollNext: boolean} & CarouselPropsconst CarouselContext = React.createContext<CarouselContextProps | null>(null)function useCarousel() {  const context = React.useContext(CarouselContext)  if (!context) {    throw new Error("useCarousel must be used within a <Carousel />")  }  return context}function Carousel({  orientation = "horizontal",  opts,  setApi,  plugins,  className,  children,  ...props}: React.ComponentProps<"div"> & CarouselProps) {  const [carouselRef, api] = useEmblaCarousel(    {      ...opts,      axis: orientation === "horizontal" ? "x" : "y",    },    plugins  )  const [canScrollPrev, setCanScrollPrev] = React.useState(false)  const [canScrollNext, setCanScrollNext] = React.useState(false)  const onSelect = React.useCallback((api: CarouselApi) => {    if (!api) return    setCanScrollPrev(api.canScrollPrev())    setCanScrollNext(api.canScrollNext())  }, [])  const scrollPrev = React.useCallback(() => {    api?.scrollPrev()  }, [api])  const scrollNext = React.useCallback(() => {    api?.scrollNext()  }, [api])  const handleKeyDown = React.useCallback(    (event: React.KeyboardEvent<HTMLDivElement>) => {      if (event.key === "ArrowLeft") {        event.preventDefault()        scrollPrev()      } else if (event.key === "ArrowRight") {        event.preventDefault()        scrollNext()      }    },    [scrollPrev, scrollNext]  )  React.useEffect(() => {    if (!api || !setApi) return    setApi(api)  }, [api, setApi])  React.useEffect(() => {    if (!api) return    onSelect(api)    api.on("reInit", onSelect)    api.on("select", onSelect)    return () => {      api?.off("select", onSelect)    }  }, [api, onSelect])  return (    <CarouselContext.Provider      value={{        carouselRef,        api: api,        opts,        orientation:          orientation || (opts?.axis === "y" ? "vertical" : "horizontal"),        scrollPrev,        scrollNext,        canScrollPrev,        canScrollNext,      }}    >      <div        onKeyDownCapture={handleKeyDown}        className={cn("relative", className)}        role="region"        aria-roledescription="carousel"        data-slot="carousel"        {...props}      >        {children}      </div>    </CarouselContext.Provider>  )}function CarouselContent({ className, ...props }: React.ComponentProps<"div">) {  const { carouselRef, orientation } = useCarousel()  return (    <div      ref={carouselRef}      className="overflow-hidden"      data-slot="carousel-content"    >      <div        className={cn(          "flex",          orientation === "horizontal" ? "-ml-4" : "-mt-4 flex-col",          className        )}        {...props}      />    </div>  )}function CarouselItem({ className, ...props }: React.ComponentProps<"div">) {  const { orientation } = useCarousel()  return (    <div      role="group"      aria-roledescription="slide"      data-slot="carousel-item"      className={cn(        "min-w-0 shrink-0 grow-0 basis-full",        orientation === "horizontal" ? "pl-4" : "pt-4",        className      )}      {...props}    />  )}function CarouselPrevious({  className,  variant = "outline",  size = "icon-sm",  ...props}: React.ComponentProps<typeof Button>) {  const { orientation, scrollPrev, canScrollPrev } = useCarousel()  return (    <Button      data-slot="carousel-previous"      variant={variant}      size={size}      className={cn(        "absolute touch-manipulation rounded-full",        orientation === "horizontal"          ? "inset-y-0 -left-12 my-auto"          : "-top-12 left-1/2 -translate-x-1/2 rotate-90",        className      )}      disabled={!canScrollPrev}      onClick={scrollPrev}      {...props}    >      <ChevronLeftIcon />      <span className="sr-only">Previous slide</span>    </Button>  )}function CarouselNext({  className,  variant = "outline",  size = "icon-sm",  ...props}: React.ComponentProps<typeof Button>) {  const { orientation, scrollNext, canScrollNext } = useCarousel()  return (    <Button      data-slot="carousel-next"      variant={variant}      size={size}      className={cn(        "absolute touch-manipulation rounded-full",        orientation === "horizontal"          ? "inset-y-0 -right-12 my-auto"          : "-bottom-12 left-1/2 -translate-x-1/2 rotate-90",        className      )}      disabled={!canScrollNext}      onClick={scrollNext}      {...props}    >      <ChevronRightIcon />      <span className="sr-only">Next slide</span>    </Button>  )}export {  type CarouselApi,  Carousel,  CarouselContent,  CarouselItem,  CarouselPrevious,  CarouselNext,  useCarousel,}
  3. Make sure the foundation is in place

    Components read their colors, radius and surfaces from the tokens in app/globals.css. See Theming.

Usage#

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

Examples#

1
2
3
4
5
Slide 0 of 0
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5
1
2
3
4
5