ComponentsChangelogPro

    Getting Started

    Animated Cards

    Components

    Backgrounds

    3d & Shaders

    Text Animations

    Buttons

    Docs
    Animated Card 2

    Animated Card 2

    An animated hover card that can showcase almost anything—it all comes down to the caption you choose.

    Loading...

    Installation

    Install dependencies

    npm install

    npm install clsx tailwind-merge

    Add utils file

    lib/utils.ts

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

    Copy the animated-card Component

    animated-card.tsx

    import * as React from "react";
    import { cn } from "@/lib/utils";
     
    interface CardProps extends React.HTMLAttributes<HTMLDivElement> {}
     
    export function AnimatedCard({ className, ...props }: CardProps) {
      return (
        <div
          className={cn(
            "rounded-lg relative overflow-hidden w-[356px] group/animated-card border dark:border-zinc-800 border-zinc-200 dark:bg-black bg-white shadow-sm",
            className
          )}
          {...props}
        />
      );
    }
     
    export function CardDetails({ className, ...props }: CardProps) {
      return (
        <div
          className={cn(
            "flex flex-col space-y-1.5 p-4 border-t dark:border-zinc-800 border-zinc-200",
            className
          )}
          {...props}
        />
      );
    }
     
    interface CardTitleProps extends React.HTMLAttributes<HTMLHeadingElement> {}
     
    export function CardTitle({ className, ...props }: CardTitleProps) {
      return (
        <h3
          className={cn(
            "text-xl dark:text-white text-black font-semibold leading-none tracking-tight",
            className
          )}
          {...props}
        />
      );
    }
     
    interface CardDescriptionProps extends React.HTMLAttributes<HTMLParagraphElement> {}
     
    export function CardDescription({ className, ...props }: CardDescriptionProps) {
      return (
        <p
          className={cn(
            "text-sm text-neutral-500 dark:text-neutral-400",
            className
          )}
          {...props}
        />
      );
    }
     
    export function CardVisual({ className, ...props }: CardProps) {
      return (
        <div
          className={cn("p-0 overflow-hidden w-full", className)}
          {...props}
        />
      );
    }

    Copy the source code

    animated-card/visual-2.tsx

    "use client";
     
    import * as React from "react";
    import { useState, useEffect } from "react";
     
    interface Visual2Props {
      mainColor?: string;
      secondaryColor?: string;
      gridColor?: string;
    }
     
    export default function Visual2({
      mainColor = "#8b5cf6",
      /*color = "#8b5cf6",*/
      secondaryColor = "#fbbf24",
      gridColor = "#80808015",
    }: Visual2Props) {
      const [hovered, setHovered] = useState(false);
     
      return (
        <>
          <div
            className="inset-0 absolute z-20"
            onMouseEnter={() => setHovered(true)}
            onMouseLeave={() => setHovered(false)}
            style={{ "--color": mainColor, "--secondary-color": secondaryColor } as React.CSSProperties}
          />
          <div className="w-[356px] h-[180px] rounded-t-lg relative overflow-hidden">
            <Layer1 hovered={hovered} color={mainColor} secondaryColor={secondaryColor} />
            <Layer2 color={mainColor} />
            <Layer3 color={mainColor} />
            <Layer4 color={mainColor} secondaryColor={secondaryColor} hovered={hovered} />
            <EllipseGradient color={mainColor} />
            <GridLayer color={gridColor} />
          </div>
        </>
      );
    }
     
    interface LayerProps {
      color: string;
      secondaryColor?: string;
      hovered?: boolean;
    }
     
    const EllipseGradient: React.FC<{ color: string }> = ({ color }) => {
      return (
        <div className="w-full h-full z-[5] absolute inset-0 flex items-center justify-center">
          <svg width="356" height="196" viewBox="0 0 356 180" fill="none" xmlns="http://www.w3.org/2000/svg">
            <rect width="356" height="180" fill="url(#paint0_radial_12_207)" />
            <defs>
              <radialGradient
                id="paint0_radial_12_207"
                cx="0"
                cy="0"
                r="1"
                gradientUnits="userSpaceOnUse"
                gradientTransform="translate(178 98) rotate(90) scale(98 178)"
              >
                <stop stopColor={color} stopOpacity="0.25" />
                <stop offset="0.34" stopColor={color} stopOpacity="0.15" />
                <stop offset="1" stopOpacity="0" />
              </radialGradient>
            </defs>
          </svg>
        </div>
      );
    };
     
    const GridLayer: React.FC<{ color: string }> = ({ color }) => {
      return (
        <div
          style={{ "--grid-color": color } as React.CSSProperties}
          className="absolute z-[4] inset-0 h-full w-full bg-transparent bg-[linear-gradient(to_right,var(--grid-color)_1px,transparent_1px),linear-gradient(to_bottom,var(--grid-color)_1px,transparent_1px)] bg-[size:20px_20px] pointer-events-none bg-center dark:[mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_60%,transparent_100%)] [mask-image:radial-gradient(ellipse_50%_50%_at_50%_50%,#000_60%,transparent_100%)]"
        />
      );
    };
     
    const Layer1: React.FC<LayerProps> = ({ hovered, color, secondaryColor }) => {
      const [mainProgress, setMainProgress] = useState(12.5);
      const [secondaryProgress, setSecondaryProgress] = useState(0);
     
      useEffect(() => {
        let timeout: NodeJS.Timeout;
     
        if (hovered) {
          timeout = setTimeout(() => {
            setMainProgress(66);
            setSecondaryProgress(100);
          }, 200);
        } else {
          setMainProgress(12.5);
          setSecondaryProgress(0);
        }
     
        return () => {
          clearTimeout(timeout);
        };
      }, [hovered]);
     
      const radius = 40;
      const circumference = 2 * Math.PI * radius;
      const mainDashoffset = circumference - (mainProgress / 100) * circumference;
      const secondaryDashoffset = circumference - (secondaryProgress / 100) * circumference;
     
      return (
        <div className="w-[356px] h-[360px] absolute top-0 left-0 transition-transform duration-500 ease-[cubic-bezier(0.6, 0.6, 0, 1)] transform group-hover/animated-card:-translate-y-[90px] z-[7] flex items-center justify-center group-hover/animated-card:scale-110">
          <div className="relative w-[120px] h-[120px] flex items-center justify-center dark:text-white text-[#00000050]">
            <div className="donut-chart-container relative">
              <svg width="120" height="120" viewBox="0 0 100 100">
                <circle cx="50" cy="50" r={radius} stroke="currentColor" strokeWidth="10" fill="transparent" opacity={0.2} />
                <circle
                  cx="50"
                  cy="50"
                  r={radius}
                  stroke={secondaryColor}
                  strokeWidth="14"
                  fill="transparent"
                  strokeDasharray={circumference}
                  strokeDashoffset={secondaryDashoffset}
                  transform="rotate(-90 50 50)"
                  style={{
                    transition: "stroke-dashoffset 0.5s cubic-bezier(0.6, 0.6, 0, 1)",
                  }}
                />
                <circle
                  cx="50"
                  cy="50"
                  r={radius}
                  stroke={color}
                  strokeWidth="14"
                  fill="transparent"
                  strokeDasharray={circumference}
                  strokeDashoffset={mainDashoffset}
                  transform="rotate(-90 50 50)"
                  style={{
                    transition: "stroke-dashoffset 0.5s cubic-bezier(0.6, 0.6, 0, 1)",
                  }}
                />
              </svg>
              <div className="absolute inset-0 flex items-center justify-center">
                <span className="text-xl dark:text-white text-black font-gilroy">
                  {hovered ? (secondaryProgress > 66 ? secondaryProgress : mainProgress) : mainProgress}%
                </span>
              </div>
            </div>
          </div>
        </div>
      );
    };
     
    const Layer2: React.FC<{ color: string }> = ({ color }) => {
      return (
        <div className="relative w-[356px] h-full" style={{ "--color": color } as React.CSSProperties}>
          <div className="bg-transparent absolute inset-0 w-[356px] translate-y-0 transition-transform duration-500 ease-[cubic-bezier(0.6, 0.6, 0, 1)] group-hover/animated-card:translate-y-full z-[6] flex p-4 items-start justify-center">
            <div className="border dark:border-zinc-800 border-zinc-200 rounded-md px-2 py-1.5 backdrop-blur-sm opacity-100 transition-opacity duration-500 ease-[cubic-bezier(0.6, 0.6, 0, 1)] group-hover/animated-card:opacity-0 dark:bg-black/25 bg-white/25">
              <div className="flex gap-2 items-center">
                <div className="h-2 w-2 bg-[var(--color)] rounded-full shrink-0" />
                <p className="dark:text-white text-black text-xs">Random Data Visualization</p>
              </div>
              <p className="text-neutral-500 dark:text-neutral-400 text-xs mt-1">Displaying some interesting stats.</p>
            </div>
          </div>
        </div>
      );
    };
     
    const Layer3: React.FC<{ color: string }> = ({ color }) => {
      return (
        <div className=" inset-0 absolute z-[6] translate-y-full opacity-0 group-hover/animated-card:translate-y-0 group-hover/animated-card:opacity-100 transition-all duration-500 ease-[cubic-bezier(0.6, 0.6, 0, 1)] flex items-center justify-center">
          <svg width="356" height="180" viewBox="0 0 356 180" fill="none" xmlns="http://www.w3.org/2000/svg">
            <rect width="356" height="180" fill="url(#paint0_linear_29_3)" />
            <defs>
              <linearGradient id="paint0_linear_29_3" x1="178" y1="0" x2="178" y2="180" gradientUnits="userSpaceOnUse">
                <stop offset="0.35" stopColor={color} stopOpacity="0" />
                <stop offset="1" stopColor={color} stopOpacity="0.3" />
              </linearGradient>
            </defs>
          </svg>
        </div>
      );
    };
     
    const Layer4: React.FC<LayerProps> = ({ color, secondaryColor, hovered }) => {
      const items = [
        { id: 1, translateX: "100", translateY: "50", text: "ReactJS" },
        { id: 2, translateX: "100", translateY: "-50", text: "MongoDB" },
        { id: 3, translateX: "125", translateY: "0", text: "Prisma" },
        { id: 4, translateX: "-125", translateY: "0", text: "NextJs" },
        { id: 5, translateX: "-100", translateY: "50", text: "Auth.js" },
        { id: 6, translateX: "-100", translateY: "-50", text: "Stripe" },
      ];
     
      return (
        <div className="inset-0 absolute z-[7] opacity-0 group-hover/animated-card:opacity-100 transition-opacity duration-500 ease-[cubic-bezier(0.6, 0.6, 0, 1)] flex items-center justify-center">
          {items.map((item, index) => (
            <div
              key={item.id}
              className="rounded-full absolute transition-all duration-500 ease-[cubic-bezier(0.6, 0.6, 0, 1)] flex items-center justify-center gap-1 backdrop-blur-sm border dark:border-zinc-800 border-zinc-200 py-0.5 px-1.5"
              style={{
                transform: hovered ? `translate(${item.translateX}px, ${item.translateY}px)` : "translate(0px, 0px)",
              }}
            >
              <div className="h-1.5 w-1.5 rounded-full" style={{ backgroundColor: index < 3 ? color : secondaryColor }} />
              <span className="text-[10px] ml-1 dark:text-white text-black">{item.text}</span>
            </div>
          ))}
        </div>
      );
    };

    Props

    animated-card props

    PropTypeDescriptionDefault
    classNameStringAdditional custom classes for styling the component.-
    propsReact.HTMLAttributes<HTMLDivElement>Additional HTML attributes for the main div element.-

    visual-2 props

    PropTypeDescriptionDefault
    mainColorStringMain color used for certain elements in the component."#8b5cf6"
    secondaryColorStringSecondary color used for other elements in the component."#fbbf24"
    gridColorStringGrid background color for the component."#80808025"

    Note

    • Many more cards are coming in the future. If you need a card for a specific theme, feel free to send me a request. If it's something that could benefit many people, I'll build it.

    Credits

  1. This component is inspired by Wope