Logo

Kaif UI

logoKaif UI
Components

Carousel

Carousel

Display content seamlessly with the smooth Carousel component.

Carousel image

Success is not in what you have, but who you are.

Carousel image

The best way to predict the future is to create it.

Carousel image

Don’t watch the clock; do what it does. Keep going.

Carousel image

Believe you can and you're halfway there.

Carousel image

Success usually comes to those who are too busy to be looking for it.

Carousel image

Opportunities don't happen, you create them.

Carousel image

Don’t be afraid to give up the good to go for the great.

Carousel image 1
Carousel image 2
Carousel image 3
Carousel image 4

Carousel With Custom Indicators

Carousel image 1
Carousel image 2
Carousel image 3
Carousel image 4

Carousel With Custom Sizes

Carousel image 1
Carousel image 2
Carousel image 3
Carousel image 4
Carousel image 5
Carousel image 6
Carousel image 7

Installation

Install Dependencies

npm i framer-motion lucide-react

Add util file

lib/utils.ts
import { clsx, type ClassValue } from "clsx";
import { twMerge } from "tailwind-merge";
 
export function cn(...inputs: ClassValue[]) {
  return twMerge(clsx(inputs));
}

Copy the source code

components/kaif-ui/carousel.tsx
"use client";
 
// Visit https://kaif-ui.vercel.app/ for more components like this
 
import {
  Children,
  ReactNode,
  createContext,
  useContext,
  useEffect,
  useRef,
  useState,
} from "react";
import { motion, Transition, useMotionValue } from "framer-motion";
import { cn } from "@/lib/utils";
import { ChevronLeft, ChevronRight } from "lucide-react";
 
type CarouselContextType = {
  index: number;
  setIndex: (newIndex: number) => void;
  itemsCount: number;
  setItemsCount: (newItemsCount: number) => void;
  disableDrag: boolean;
};
 
const CarouselContext = createContext<CarouselContextType | undefined>(
  undefined
);
 
function useCarousel() {
  const context = useContext(CarouselContext);
  if (!context) {
    throw new Error("useCarousel must be used within an CarouselProvider");
  }
  return context;
}
 
type CarouselProviderProps = {
  children: ReactNode;
  initialIndex?: number;
  onIndexChange?: (newIndex: number) => void;
  disableDrag?: boolean;
};
 
function CarouselProvider({
  children,
  initialIndex = 0,
  onIndexChange,
  disableDrag = false,
}: CarouselProviderProps) {
  const [index, setIndex] = useState<number>(initialIndex);
  const [itemsCount, setItemsCount] = useState<number>(0);
 
  const handleSetIndex = (newIndex: number) => {
    setIndex(newIndex);
    onIndexChange?.(newIndex);
  };
 
  useEffect(() => {
    setIndex(initialIndex);
  }, [initialIndex]);
 
  return (
    <CarouselContext.Provider
      value={{
        index,
        setIndex: handleSetIndex,
        itemsCount,
        setItemsCount,
        disableDrag,
      }}
    >
      {children}
    </CarouselContext.Provider>
  );
}
 
type CarouselProps = {
  children: ReactNode;
  className?: string;
  initialIndex?: number;
  index?: number;
  onIndexChange?: (newIndex: number) => void;
  disableDrag?: boolean;
};
 
function Carousel({
  children,
  className,
  initialIndex = 0,
  index: externalIndex,
  onIndexChange,
  disableDrag = false,
}: CarouselProps) {
  const [internalIndex, setInternalIndex] = useState<number>(initialIndex);
  const isControlled = externalIndex !== undefined;
  const currentIndex = isControlled ? externalIndex : internalIndex;
 
  const handleIndexChange = (newIndex: number) => {
    if (!isControlled) {
      setInternalIndex(newIndex);
    }
    onIndexChange?.(newIndex);
  };
 
  return (
    <CarouselProvider
      initialIndex={currentIndex}
      onIndexChange={handleIndexChange}
      disableDrag={disableDrag}
    >
      <div className={cn("group/hover relative", className)}>
        <div className="overflow-hidden">{children}</div>
      </div>
    </CarouselProvider>
  );
}
 
type CarouselNavigationProps = {
  className?: string;
  classNameButton?: string;
  alwaysShow?: boolean;
};
 
function CarouselNavigation({
  className,
  classNameButton,
  alwaysShow,
}: CarouselNavigationProps) {
  const { index, setIndex, itemsCount } = useCarousel();
 
  return (
    <div
      className={cn(
        "pointer-events-none absolute left-[-12.5%] top-1/2 flex w-[125%] -translate-y-1/2 justify-between px-2",
        className
      )}
    >
      <button
        type="button"
        className={cn(
          "pointer-events-auto h-fit w-fit rounded-full bg-zinc-50 p-2 transition-opacity duration-300 dark:bg-zinc-950",
          alwaysShow
            ? "opacity-100"
            : "opacity-0 group-hover/hover:opacity-100",
          alwaysShow
            ? "disabled:opacity-40"
            : "disabled:group-hover/hover:opacity-40",
          classNameButton
        )}
        disabled={index === 0}
        onClick={() => {
          if (index > 0) {
            setIndex(index - 1);
          }
        }}
      >
        <ChevronLeft
          className="stroke-zinc-600 dark:stroke-zinc-50"
          size={16}
        />
      </button>
      <button
        type="button"
        className={cn(
          "pointer-events-auto h-fit w-fit rounded-full bg-zinc-50 p-2 transition-opacity duration-300 dark:bg-zinc-950",
          alwaysShow
            ? "opacity-100"
            : "opacity-0 group-hover/hover:opacity-100",
          alwaysShow
            ? "disabled:opacity-40"
            : "disabled:group-hover/hover:opacity-40",
          classNameButton
        )}
        disabled={index + 1 === itemsCount}
        onClick={() => {
          if (index < itemsCount - 1) {
            setIndex(index + 1);
          }
        }}
      >
        <ChevronRight
          className="stroke-zinc-600 dark:stroke-zinc-50"
          size={16}
        />
      </button>
    </div>
  );
}
 
type CarouselIndicatorProps = {
  className?: string;
  classNameButton?: string;
};
 
function CarouselIndicator({
  className,
  classNameButton,
}: CarouselIndicatorProps) {
  const { index, itemsCount, setIndex } = useCarousel();
 
  return (
    <div
      className={cn(
        "absolute bottom-0 z-10 flex w-full items-center justify-center",
        className
      )}
    >
      <div className="flex space-x-2">
        {Array.from({ length: itemsCount }, (_, i) => (
          <button
            key={i}
            type="button"
            aria-label={`Go to slide ${i + 1}`}
            onClick={() => setIndex(i)}
            className={cn(
              "h-2 w-2 rounded-full transition-opacity duration-300",
              index === i
                ? "bg-zinc-950 dark:bg-zinc-50"
                : "bg-zinc-900/50 dark:bg-zinc-100/50",
              classNameButton
            )}
          />
        ))}
      </div>
    </div>
  );
}
 
type CarouselContentProps = {
  children: ReactNode;
  className?: string;
  transition?: Transition;
};
 
function CarouselContent({
  children,
  className,
  transition,
}: CarouselContentProps) {
  const { index, setIndex, setItemsCount, disableDrag } = useCarousel();
  const [visibleItemsCount, setVisibleItemsCount] = useState(1);
  const dragX = useMotionValue(0);
  const containerRef = useRef<HTMLDivElement>(null);
  const itemsLength = Children.count(children);
 
  useEffect(() => {
    if (!containerRef.current) {
      return;
    }
 
    const options = {
      root: containerRef.current,
      threshold: 0.5,
    };
 
    const observer = new IntersectionObserver((entries) => {
      const visibleCount = entries.filter(
        (entry) => entry.isIntersecting
      ).length;
      setVisibleItemsCount(visibleCount);
    }, options);
 
    const childNodes = containerRef.current.children;
    Array.from(childNodes).forEach((child) => observer.observe(child));
 
    return () => observer.disconnect();
  }, [children, setItemsCount]);
 
  useEffect(() => {
    if (!itemsLength) {
      return;
    }
 
    setItemsCount(itemsLength);
  }, [itemsLength, setItemsCount]);
 
  const onDragEnd = () => {
    const x = dragX.get();
 
    if (x <= -10 && index < itemsLength - 1) {
      setIndex(index + 1);
    } else if (x >= 10 && index > 0) {
      setIndex(index - 1);
    }
  };
 
  return (
    <motion.div
      drag={disableDrag ? false : "x"}
      dragConstraints={
        disableDrag
          ? undefined
          : {
              left: 0,
              right: 0,
            }
      }
      dragMomentum={disableDrag ? undefined : false}
      style={{
        x: disableDrag ? undefined : dragX,
      }}
      animate={{
        translateX: `-${index * (100 / visibleItemsCount)}%`,
      }}
      onDragEnd={disableDrag ? undefined : onDragEnd}
      transition={
        transition || {
          damping: 18,
          stiffness: 90,
          type: "spring",
          duration: 0.2,
        }
      }
      className={cn(
        "flex items-center",
        !disableDrag && "cursor-grab active:cursor-grabbing",
        className
      )}
      ref={containerRef}
    >
      {children}
    </motion.div>
  );
}
 
type CarouselItemProps = {
  children: ReactNode;
  className?: string;
};
 
function CarouselItem({ children, className }: CarouselItemProps) {
  return (
    <motion.div
      className={cn(
        "w-full min-w-0 shrink-0 grow-0 overflow-hidden",
        className
      )}
    >
      {children}
    </motion.div>
  );
}
 
export {
  Carousel,
  CarouselContent,
  CarouselNavigation,
  CarouselIndicator,
  CarouselItem,
  useCarousel,
};

Tech Stack

Props

CarouselContextType

PropTypeDefault
index
number
-
setIndex
(newIndex: number) => void
-
itemsCount
number
-
setItemsCount
(newItemsCount: number) => void
-
disableDrag
boolean
-

CarouselProviderProps

PropTypeDefault
children
ReactNode
-
initialIndex
number
0
onIndexChange
(newIndex: number) => void
-
disableDrag
boolean
false

CarouselProps

PropTypeDefault
children
ReactNode
-
className
string
-
initialIndex
number
0
index
number
-
onIndexChange
(newIndex: number) => void
-
disableDrag
boolean
false

CarouselNavigationProps

PropTypeDefault
className
string
-
classNameButton
string
-
alwaysShow
boolean
-

CarouselIndicatorProps

PropTypeDefault
className
string
-
classNameButton
string
-

CarouselContentProps

PropTypeDefault
children
ReactNode
-
className
string
-
transition
Transition
-

CarouselItemProps

PropTypeDefault
children
ReactNode
-
className
string
-

On this page

Brought to you by Muhammad Kaif Nazeer