Web InnoventixFreeCode

Card Glow Hover Effect

Original · free

A card with a coloured glow that blooms on hover.

byWeb InnoventixReact + Tailwind
hovercardglow
Open

Copy prompt gives Claude Code, Cursor or v0 a ready-to-paste prompt that recreates this exact component.

npx shadcn@latest add https://webinnoventix.com/r/hover-card-glow.json
hover-card-glow.tsx
"use client";

import { useState, useRef, useId } from "react";

type Glow = "indigo" | "violet" | "emerald" | "rose" | "amber" | "sky";

type Card = {
  id: string;
  glow: Glow;
  eyebrow: string;
  title: string;
  body: string;
  meta: string;
  icon: "spark" | "shield" | "pulse";
};

const CARDS: Card[] = [
  {
    id: "latency",
    glow: "indigo",
    eyebrow: "Edge network",
    title: "Sub-40ms cold starts",
    body: "Functions boot at the closest region to your visitor, so the first byte lands before the spinner ever shows.",
    meta: "312 regions live",
    icon: "spark",
  },
  {
    id: "guardrails",
    glow: "emerald",
    eyebrow: "Zero-config safety",
    title: "Rollbacks that never lose data",
    body: "Every deploy is atomic. If a release misbehaves, one click restores the exact prior state — schema and all.",
    meta: "1-click revert",
    icon: "shield",
  },
  {
    id: "signals",
    glow: "rose",
    eyebrow: "Live telemetry",
    title: "See every request in flight",
    body: "Trace a slow call from the browser down to the query that caused it, streamed in real time with no sampling.",
    meta: "0% sampling",
    icon: "pulse",
  },
];

const GLOW: Record<
  Glow,
  { bloom: string; ring: string; text: string; dot: string; chip: string }
> = {
  indigo: {
    bloom:
      "from-indigo-400/40 via-indigo-500/20 dark:from-indigo-500/45 dark:via-indigo-400/25",
    ring: "group-hover:border-indigo-300/70 group-focus-visible:border-indigo-300/70 dark:group-hover:border-indigo-400/50 dark:group-focus-visible:border-indigo-400/50",
    text: "text-indigo-600 dark:text-indigo-300",
    dot: "bg-indigo-500 dark:bg-indigo-400",
    chip: "bg-indigo-50 text-indigo-700 ring-indigo-200 dark:bg-indigo-500/10 dark:text-indigo-300 dark:ring-indigo-400/20",
  },
  violet: {
    bloom:
      "from-violet-400/40 via-violet-500/20 dark:from-violet-500/45 dark:via-violet-400/25",
    ring: "group-hover:border-violet-300/70 group-focus-visible:border-violet-300/70 dark:group-hover:border-violet-400/50 dark:group-focus-visible:border-violet-400/50",
    text: "text-violet-600 dark:text-violet-300",
    dot: "bg-violet-500 dark:bg-violet-400",
    chip: "bg-violet-50 text-violet-700 ring-violet-200 dark:bg-violet-500/10 dark:text-violet-300 dark:ring-violet-400/20",
  },
  emerald: {
    bloom:
      "from-emerald-400/40 via-emerald-500/20 dark:from-emerald-500/45 dark:via-emerald-400/25",
    ring: "group-hover:border-emerald-300/70 group-focus-visible:border-emerald-300/70 dark:group-hover:border-emerald-400/50 dark:group-focus-visible:border-emerald-400/50",
    text: "text-emerald-600 dark:text-emerald-300",
    dot: "bg-emerald-500 dark:bg-emerald-400",
    chip: "bg-emerald-50 text-emerald-700 ring-emerald-200 dark:bg-emerald-500/10 dark:text-emerald-300 dark:ring-emerald-400/20",
  },
  rose: {
    bloom:
      "from-rose-400/40 via-rose-500/20 dark:from-rose-500/45 dark:via-rose-400/25",
    ring: "group-hover:border-rose-300/70 group-focus-visible:border-rose-300/70 dark:group-hover:border-rose-400/50 dark:group-focus-visible:border-rose-400/50",
    text: "text-rose-600 dark:text-rose-300",
    dot: "bg-rose-500 dark:bg-rose-400",
    chip: "bg-rose-50 text-rose-700 ring-rose-200 dark:bg-rose-500/10 dark:text-rose-300 dark:ring-rose-400/20",
  },
  amber: {
    bloom:
      "from-amber-400/40 via-amber-500/20 dark:from-amber-500/45 dark:via-amber-400/25",
    ring: "group-hover:border-amber-300/70 group-focus-visible:border-amber-300/70 dark:group-hover:border-amber-400/50 dark:group-focus-visible:border-amber-400/50",
    text: "text-amber-600 dark:text-amber-300",
    dot: "bg-amber-500 dark:bg-amber-400",
    chip: "bg-amber-50 text-amber-700 ring-amber-200 dark:bg-amber-500/10 dark:text-amber-300 dark:ring-amber-400/20",
  },
  sky: {
    bloom:
      "from-sky-400/40 via-sky-500/20 dark:from-sky-500/45 dark:via-sky-400/25",
    ring: "group-hover:border-sky-300/70 group-focus-visible:border-sky-300/70 dark:group-hover:border-sky-400/50 dark:group-focus-visible:border-sky-400/50",
    text: "text-sky-600 dark:text-sky-300",
    dot: "bg-sky-500 dark:bg-sky-400",
    chip: "bg-sky-50 text-sky-700 ring-sky-200 dark:bg-sky-500/10 dark:text-sky-300 dark:ring-sky-400/20",
  },
};

function Icon({ name, className }: { name: Card["icon"]; className?: string }) {
  if (name === "shield") {
    return (
      <svg
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        strokeWidth={1.75}
        strokeLinecap="round"
        strokeLinejoin="round"
        className={className}
        aria-hidden="true"
      >
        <path d="M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3z" />
        <path d="M9 12l2 2 4-4" />
      </svg>
    );
  }
  if (name === "pulse") {
    return (
      <svg
        viewBox="0 0 24 24"
        fill="none"
        stroke="currentColor"
        strokeWidth={1.75}
        strokeLinecap="round"
        strokeLinejoin="round"
        className={className}
        aria-hidden="true"
      >
        <path d="M3 12h4l2 6 4-14 2 8h6" />
      </svg>
    );
  }
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={1.75}
      strokeLinecap="round"
      strokeLinejoin="round"
      className={className}
      aria-hidden="true"
    >
      <path d="M12 3v4M12 17v4M3 12h4M17 12h4M6 6l2.5 2.5M15.5 15.5L18 18M18 6l-2.5 2.5M8.5 15.5L6 18" />
      <circle cx="12" cy="12" r="2.5" />
    </svg>
  );
}

function Arrow({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      className={className}
      aria-hidden="true"
    >
      <path d="M5 12h14M13 6l6 6-6 6" />
    </svg>
  );
}

export default function HoverCardGlow() {
  const [active, setActive] = useState<string | null>(null);
  const gridRef = useRef<HTMLUListElement>(null);
  const uid = useId().replace(/[:]/g, "");
  const prefix = `hcg-${uid}`;

  return (
    <section className="relative w-full overflow-hidden bg-slate-50 px-6 py-20 sm:py-28 dark:bg-slate-950">
      <style>{`
        @keyframes ${prefix}-drift {
          0%, 100% { transform: translate3d(0, 0, 0) scale(1); }
          50% { transform: translate3d(0, -6px, 0) scale(1.04); }
        }
        @keyframes ${prefix}-rise {
          from { opacity: 0; transform: translateY(14px); }
          to { opacity: 1; transform: translateY(0); }
        }
        .${prefix}-rise { animation: ${prefix}-rise 0.6s cubic-bezier(0.22, 1, 0.36, 1) both; }
        .${prefix}-drift { animation: ${prefix}-drift 6s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .${prefix}-rise, .${prefix}-drift { animation: none !important; }
        }
      `}</style>

      <div className="relative mx-auto max-w-6xl">
        <div className={`${prefix}-rise mx-auto mb-14 max-w-2xl text-center`}>
          <span className="inline-flex items-center gap-2 rounded-full bg-white px-3 py-1 text-xs font-medium text-slate-600 ring-1 ring-slate-200 dark:bg-slate-900 dark:text-slate-300 dark:ring-slate-800">
            <span className="relative flex h-2 w-2">
              <span className="absolute inline-flex h-full w-full animate-ping rounded-full bg-emerald-400 opacity-70" />
              <span className="relative inline-flex h-2 w-2 rounded-full bg-emerald-500" />
            </span>
            Platform, shipped
          </span>
          <h2 className="mt-5 text-3xl font-semibold tracking-tight text-slate-900 sm:text-4xl dark:text-white">
            Everything glows when it works
          </h2>
          <p className="mt-4 text-base leading-relaxed text-slate-600 dark:text-slate-400">
            Hover or focus any card to see the surface come alive. Built for
            keyboards, screen readers, and reduced-motion the same day it was
            built for the mouse.
          </p>
        </div>

        <ul
          ref={gridRef}
          className="grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3"
        >
          {CARDS.map((card, i) => {
            const g = GLOW[card.glow];
            const isActive = active === card.id;
            return (
              <li
                key={card.id}
                className={`${prefix}-rise`}
                style={{ animationDelay: `${120 + i * 90}ms` }}
              >
                <a
                  href="#"
                  aria-label={`${card.title} — learn more`}
                  onMouseEnter={() => setActive(card.id)}
                  onMouseLeave={() => setActive((p) => (p === card.id ? null : p))}
                  onFocus={() => setActive(card.id)}
                  onBlur={() => setActive((p) => (p === card.id ? null : p))}
                  className={`group relative flex h-full flex-col overflow-hidden rounded-3xl border border-slate-200 bg-white p-6 outline-none transition-[transform,box-shadow,border-color] duration-500 ease-out hover:-translate-y-1.5 focus-visible:-translate-y-1.5 focus-visible:ring-2 focus-visible:ring-slate-400 focus-visible:ring-offset-2 focus-visible:ring-offset-slate-50 dark:border-slate-800 dark:bg-slate-900 dark:focus-visible:ring-slate-500 dark:focus-visible:ring-offset-slate-950 ${g.ring}`}
                >
                  {/* Blooming glow */}
                  <span
                    aria-hidden="true"
                    className={`pointer-events-none absolute -inset-px z-0 rounded-3xl bg-gradient-to-br to-transparent opacity-0 blur-2xl transition-opacity duration-500 ease-out group-hover:opacity-100 group-focus-visible:opacity-100 ${g.bloom} ${
                      isActive ? `${prefix}-drift` : ""
                    }`}
                  />
                  {/* Sheen line */}
                  <span
                    aria-hidden="true"
                    className="pointer-events-none absolute inset-x-6 top-0 z-10 h-px bg-gradient-to-r from-transparent via-slate-300/80 to-transparent opacity-0 transition-opacity duration-500 group-hover:opacity-100 group-focus-visible:opacity-100 dark:via-white/30"
                  />

                  <div className="relative z-10 flex flex-1 flex-col">
                    <div className="flex items-center justify-between">
                      <span
                        className={`flex h-11 w-11 items-center justify-center rounded-2xl ring-1 transition-transform duration-500 group-hover:scale-110 group-focus-visible:scale-110 ${g.chip} ${g.text}`}
                      >
                        <Icon name={card.icon} className="h-5 w-5" />
                      </span>
                      <span
                        className={`inline-flex items-center gap-1.5 text-xs font-medium ${g.text}`}
                      >
                        <span className={`h-1.5 w-1.5 rounded-full ${g.dot}`} />
                        {card.meta}
                      </span>
                    </div>

                    <p
                      className={`mt-6 text-xs font-semibold uppercase tracking-wider ${g.text}`}
                    >
                      {card.eyebrow}
                    </p>
                    <h3 className="mt-1.5 text-lg font-semibold tracking-tight text-slate-900 dark:text-white">
                      {card.title}
                    </h3>
                    <p className="mt-2.5 flex-1 text-sm leading-relaxed text-slate-600 dark:text-slate-400">
                      {card.body}
                    </p>

                    <span className="mt-6 inline-flex items-center gap-1.5 text-sm font-medium text-slate-500 transition-colors duration-300 dark:text-slate-400">
                      <span
                        className={`transition-colors duration-300 ${
                          isActive ? g.text : ""
                        }`}
                      >
                        Explore
                      </span>
                      <Arrow className="h-4 w-4 transition-transform duration-300 group-hover:translate-x-1 group-focus-visible:translate-x-1" />
                    </span>
                  </div>
                </a>
              </li>
            );
          })}
        </ul>
      </div>
    </section>
  );
}

Dependencies

None (React + Tailwind only).

Licence

Built by Web Innoventix. Free for personal and commercial use, no attribution required.

Built by Web Innoventix

Need a custom interface, a full website, or SEO that gets you cited by AI? We design, build and rank it end to end.

Get a free quote

Similar components

Browse all →