shadimage
bits-ui
componenthero-carouselsource: ok · refreshed

Hero Carousel

A 3-step hero carousel with animated step cards for landing pages.

npx shadcn add "https://bits-ui.netlify.app/r/hero-carousel.json"
free

Where it goes

hero

Interaction

carousel

Tags auto-generated by rules on 9/24/2026 — empty means no evidence, not lack of support.

Dependencies

Bundle weight · medium
animationframer-motion
iconslucide-react

Files2 · source cached 10/8/2026, 6:15:02 AM

registry/new-york/ui/hero-carousel.tsx
// ./registry/new-york/ui/hero-carousel.tsx

"use client";

import React, { useState } from "react";
import { motion } from "framer-motion";
import { ChevronLeft, ChevronRight } from "lucide-react";
import { Button } from "@/components/ui/button";
import { StepCard } from "./step-card";

const steps = [1, 2, 3] as const;

export function HeroCarousel() {
  const [currentIndex, setCurrentIndex] = useState(0);

  const handleNext = () => {
    setCurrentIndex((prev) => (prev + 1) % steps.length);
  };

  const handlePrev = () => {
    setCurrentIndex((prev) => (prev - 1 + steps.length) % steps.length);
  };

  const getPosition = (index: number) => {
    if (index === currentIndex) return "center";
    if (index === (currentIndex + 1) % steps.length) return "right";
    return "left";
  };

  const variants = {
    center: { x: "0%", scale: 1, zIndex: 10, opacity: 1 },
    right: { x: "50%", scale: 0.8, zIndex: 5, opacity: 0.4 },
    left: { x: "-50%", scale: 0.8, zIndex: 5, opacity: 0.4 },
  };

  return (
    <div className="relative w-full max-w-lg mx-auto h-[400px] flex items-center justify-center">
      {/* Controls */}
      <div className="absolute inset-x-0 top-1/2 -translate-y-1/2 flex justify-between px-4 z-30 pointer-events-none">
        <Button
          size="icon"
          onClick={handlePrev}
          className="pointer-events-auto w-12 h-12 rounded-full bg-white/10 backdrop-blur-md border border-white/20 text-white hover:bg-white/20 transition-all"
        >
          <ChevronLeft className="w-6 h-6" />
        </Button>
        <Button
          size="icon"
          onClick={handleNext}
          className="pointer-events-auto w-12 h-12 rounded-full bg-white/10 backdrop-blur-md border border-white/20 text-white hover:bg-white/20 transition-all"
        >
          <ChevronRight className="w-6 h-6" />
        </Button>
      </div>

      <div className="relative w-full h-full flex items-center justify-center perspective-1000">
        {steps.map((step, index) => (
          <motion.div
            key={step}
            initial="center"
            animate={getPosition(index)}
            variants={variants}
            transition={{ duration: 0.5, type: "spring", stiffness: 300, damping: 30 }}
            className="absolute w-[85%] sm:w-[70%] h-full"
          >
            <StepCard step={step} />
          </motion.div>
        ))}
      </div>
    </div>
  );
}
registry/new-york/ui/step-card.tsx
// ./registry/new-york/ui/step-card.tsx

"use client";

import { motion } from "framer-motion";
import { SearchNormal } from "iconsax-react";
import { Search, MousePointer2 } from "lucide-react";

interface StepCardProps {
  step: 1 | 2 | 3;
}

export function StepCard({ step }: StepCardProps) {
  return (
    <div className="relative group overflow-hidden rounded-2xl bg-[#09090b] border border-white/10 p-6 flex flex-col justify-between h-full min-h-[300px]">
      {/* Background Gradient */}
      <div
        className={`absolute inset-0 bg-linear-to-br opacity-0 group-hover:opacity-100 transition-opacity duration-500 ${step === 1 ? "from-primary/5 to-transparent" :
          step === 2 ? "from-purple-500/5 to-transparent" :
            "from-green-500/5 to-transparent"
          }`}
      />

      {/* Interactive Element */}
      <div className="relative h-40 mb-6 flex items-center justify-center">
        {step === 1 && (
          <div className="relative">
            <motion.div
              animate={{ y: [0, -10, 0] }}
              transition={{ duration: 4, repeat: Infinity, ease: "easeInOut" }}
              className="bg-white/5 backdrop-blur-md border border-white/10 rounded-xl p-4 shadow-2xl transform -rotate-6 z-10 relative"
            >
              <div className="flex items-center gap-3 mb-2">
                <div className="h-8 w-8 rounded-full bg-blue-500/20 flex items-center justify-center text-blue-400">
                  <SearchNormal variant="TwoTone" color="currentColor" size={16} />
                </div>
                <div className="h-2 w-20 bg-white/10 rounded-full" />
              </div>
              <div className="space-y-2">
                <div className="h-2 w-32 bg-white/5 rounded-full" />
                <div className="h-2 w-24 bg-white/5 rounded-full" />
              </div>
            </motion.div>

            <motion.div
              animate={{ y: [0, 10, 0] }}
              transition={{ duration: 5, repeat: Infinity, ease: "easeInOut", delay: 1 }}
              className="absolute top-4 -right-12 bg-white/5 backdrop-blur-md border border-white/10 rounded-xl p-4 shadow-2xl transform rotate-6 scale-90 opacity-60"
            >
              <div className="flex items-center gap-3 mb-2">
                <div className="h-8 w-8 rounded-full bg-purple-500/20 flex items-center justify-center text-purple-400">
                  <MousePointer2 size={16} />
                </div>
                <div className="h-2 w-20 bg-white/10 rounded-full" />
              </div>
              <div className="space-y-2">
                <div className="h-2 w-32 bg-white/5 rounded-full" />
              </div>
            </motion.div>
          </div>
        )}

        {step === 2 && (
          <>
            <div className="grid grid-cols-2 gap-3">
              {[1, 2, 3, 4].map((i) => (
                <motion.div
                  key={i}
                  initial={{ opacity: 0.5, scale: 0.9 }}
                  whileHover={{ scale: 1.05, opacity: 1, backgroundColor: "rgba(255,255,255,0.1)" }}
                  className="bg-white/5 border border-white/10 rounded-lg p-3 w-24 h-16 flex flex-col justify-center items-center gap-2 cursor-pointer transition-colors"
                >
                  <div className={`h-2 w-12 rounded-full ${i === 2 ? 'bg-green-500' : 'bg-white/10'}`} />
                  <div className="h-1 w-8 bg-white/5 rounded-full" />
                </motion.div>
              ))}
            </div>
            <motion.div
              className="absolute inset-0 flex items-center justify-center pointer-events-none"
              animate={{ rotate: 360 }}
              transition={{ duration: 20, repeat: Infinity, ease: "linear" }}
            >
              <div className="absolute top-0 w-full h-full border border-dashed border-white/5 rounded-full scale-150" />
            </motion.div>
          </>
        )}

        {step === 3 && (
          <div className="flex items-end justify-center gap-2 px-8 h-full">
            {[40, 70, 50, 90, 60].map((height, i) => (
              <motion.div
                key={i}
                initial={{ height: 0 }}
                whileInView={{ height: `${height}%` }}
                transition={{ duration: 1, delay: i * 0.1 }}
                className="w-8 bg-linear-to-t from-primary/20 to-primary rounded-t-sm relative group/bar"
              >
                <div className="absolute -top-8 left-1/2 -translate-x-1/2 bg-white text-black text-[10px] font-bold px-1.5 py-0.5 rounded opacity-0 group-hover/bar:opacity-100 transition-opacity">
                  {height}%
                </div>
              </motion.div>
            ))}
          </div>
        )}
      </div>

      <div className="relative z-10">
        <div className="text-xs font-bold text-primary mb-2 uppercase tracking-wider">Step {step}</div>
        <h3 className="text-xl font-bold text-white mb-2">
          {step === 1 ? "Select Your Niche" : step === 2 ? "AI Analysis" : "Launch & Profit"}
        </h3>
        <p className="text-sm text-muted-foreground">
          {step === 1 ? "Choose from high-profit categories like Pets, Beauty, or Home Decor." :
            step === 2 ? "Our agents scan thousands of ads, analyze copy, and score potential winners." :
              "Get a curated list of high-scoring products with ad creatives ready to launch."}
        </p>
      </div>
    </div>
  );
}

More from bits-ui

Browse all components in this library →