appica-dev

Carousel

Component 2

About

A draggable, swipeable image/content slider with arrows, dot pagination, thumbnails, autoplay, fade, and auto-height, for showcasing photo galleries or product shots one slide at a time.

Built with React and Tailwind. Copy the prompt and paste it into Claude Code, Cursor or Lovable: your agent adds Carousel to your project and fits it to your theme.

Code

How Carousel is used: the demo's code. The component's own source comes with a free account.

Usage.tsx

"use client";

import * as React from "react";
import {
  Carousel,
  CarouselContent,
  CarouselSlide,
  CarouselPrev,
  CarouselNext,
  type CarouselApi,
} from "@/components/ui/carousel";
import { Button } from "@appica/ui-react/button";
import { ChevronLeft, ChevronRight } from "@appica/icons-react";

const SLIDES = [1, 2, 3, 4, 5, 6];
const PARALLAX_FACTOR = 1.7;

export default function CarouselParallax() {
  const [api, setApi] = React.useState<CarouselApi>();

  React.useEffect(() => {
    if (!api) return;
    const layers = api
      .slideNodes()
      .map((slide) =>
        slide.querySelector<HTMLElement>("[data-parallax-layer]"),
      );

    const tween = (_api: CarouselApi, event?: { type: string }) => {
      const engine = api.internalEngine();
      const scrollProgress = api.scrollProgress();
      const slidesInView = api.slidesInView();
      const isScroll = event?.type === "scroll";

      api.snapList().forEach((snap, snapIndex) => {
        let diffToTarget = snap - scrollProgress;
        const slidesInSnap =
          engine.scrollSnapList.slidesBySnap[snapIndex] ?? [];

        slidesInSnap.forEach((slideIndex) => {
          if (isScroll && !slidesInView.includes(slideIndex)) return;
          if (engine.options.loop) {
            engine.slideLooper.loopPoints.forEach((loopItem) => {
              const target = loopItem.target();
              if (slideIndex === loopItem.index && target !== 0) {
                const sign = Math.sign(target);
                if (sign === -1) diffToTarget = snap - (1 + scrollProgress);
                if (sign === 1) diffToTarget = snap + (1 - scrollProgress);
              }
            });
          }
          const translate = diffToTarget * (-1 * PARALLAX_FACTOR) * 100;
          const layer = layers[slideIndex];
          if (layer) layer.style.transform = `translateX(${translate}%)`;
        });
      });
    };

    tween(api);
    api.on("scroll", tween);
    api.on("slidefocus", tween);
    api.on("reinit", tween);
    return () => {
      api.off("scroll", tween);
      api.off("slidefocus", tween);
      api.off("reinit", tween);
    };
  }, [api]);

  return (
    <div className="w-full max-w-xl">
      <Carousel loop align="center" setApi={setApi}>
        <CarouselContent>
          {SLIDES.map((n) => (
            <CarouselSlide key={n} className="basis-2/3">
              <div className="aspect-3/2 overflow-hidden rounded-xl">
                <div data-parallax-layer className="size-full">
                  <img
                    src={`https://appica.dev/carousel/slide-${n}.jpg`}
                    alt={`Slide ${n}`}
                    className="size-full scale-[1.6] object-cover"
                  />
                </div>
              </div>
            </CarouselSlide>
          ))}
        </CarouselContent>
        <div className="mt-5 flex justify-center gap-2">
          <CarouselPrev
            position="none"
            render={
              <Button variant="outline" size="icon-md">
                <ChevronLeft />
              </Button>
            }
          />
          <CarouselNext
            position="none"
            render={
              <Button variant="outline" size="icon-md">
                <ChevronRight />
              </Button>
            }
          />
        </div>
      </Carousel>
    </div>
  );
}

Dependencies

More by appica-dev

View all
  • Border Beam

    Border Beam

    42
  • Text Animate

    Text Animate

    19
  • Sparkline

    Sparkline

    7
  • Table of Contents

    Table of Contents

    6
  • Color Area

    Color Area

    6
  • Avatar

    Avatar

    6
  • Background Pattern

    Background Pattern

    3
  • Autocomplete

    Autocomplete

    2