Web InnoventixFreeCode

Card Gradient Shift Hover Effect

Original · free

A card whose gradient background shifts on hover.

byWeb InnoventixReact + Tailwind
hovercardgradientshift
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-gradient-shift.json
hover-card-gradient-shift.tsx
"use client";

import { useState } from "react";

type Plan = {
  id: string;
  name: string;
  tagline: string;
  price: string;
  period: string;
  features: string[];
  from: string;
  to: string;
  hoverFrom: string;
  hoverTo: string;
  accent: string;
};

const PLANS: Plan[] = [
  {
    id: "starter",
    name: "Starter",
    tagline: "For solo builders shipping their first product.",
    price: "$0",
    period: "/mo",
    features: ["1 project", "5k monthly requests", "Community support"],
    from: "from-slate-100",
    to: "to-slate-200",
    hoverFrom: "from-sky-400",
    hoverTo: "to-indigo-500",
    accent: "text-sky-600 dark:text-sky-300",
  },
  {
    id: "growth",
    name: "Growth",
    tagline: "For teams scaling past their first thousand users.",
    price: "$29",
    period: "/mo",
    features: ["Unlimited projects", "500k monthly requests", "Priority email support"],
    from: "from-indigo-100",
    to: "to-violet-200",
    hoverFrom: "from-violet-500",
    hoverTo: "to-fuchsia-600",
    accent: "text-violet-600 dark:text-violet-300",
  },
  {
    id: "scale",
    name: "Scale",
    tagline: "For companies that need throughput and guarantees.",
    price: "$99",
    period: "/mo",
    features: ["Dedicated infra", "10M monthly requests", "24/7 SLA support"],
    from: "from-emerald-100",
    to: "to-teal-200",
    hoverFrom: "from-emerald-500",
    hoverTo: "to-cyan-600",
    accent: "text-emerald-600 dark:text-emerald-300",
  },
];

export default function HoverCardGradientShift() {
  const [active, setActive] = useState<string | null>(null);

  return (
    <section className="relative w-full bg-white py-20 px-6 dark:bg-neutral-950 sm:py-28">
      <style>{`
        @keyframes hcgs-sheen {
          0% { transform: translateX(-120%) skewX(-12deg); opacity: 0; }
          40% { opacity: 0.55; }
          100% { transform: translateX(220%) skewX(-12deg); opacity: 0; }
        }
        .hcgs-card {
          transition: transform 420ms cubic-bezier(0.22, 1, 0.36, 1),
                      box-shadow 420ms cubic-bezier(0.22, 1, 0.36, 1);
        }
        .hcgs-grad {
          background-size: 220% 220%;
          background-position: 0% 50%;
          transition: background-position 700ms cubic-bezier(0.22, 1, 0.36, 1),
                      opacity 500ms ease;
        }
        .hcgs-card:hover .hcgs-grad,
        .hcgs-card:focus-within .hcgs-grad {
          background-position: 100% 50%;
        }
        .hcgs-sheen {
          animation: hcgs-sheen 1100ms cubic-bezier(0.22, 1, 0.36, 1);
        }
        @media (prefers-reduced-motion: reduce) {
          .hcgs-card { transition: box-shadow 200ms ease; }
          .hcgs-grad { transition: opacity 200ms ease; }
          .hcgs-sheen { animation: none; }
        }
      `}</style>

      <div className="mx-auto max-w-6xl">
        <div className="mx-auto mb-14 max-w-2xl text-center">
          <span className="inline-block rounded-full border border-indigo-200 bg-indigo-50 px-3 py-1 text-xs font-medium tracking-wide text-indigo-700 dark:border-indigo-500/30 dark:bg-indigo-500/10 dark:text-indigo-300">
            Pricing
          </span>
          <h2 className="mt-5 text-3xl font-semibold tracking-tight text-neutral-900 dark:text-white sm:text-4xl">
            Pick the plan, watch it come alive
          </h2>
          <p className="mt-4 text-base leading-relaxed text-neutral-600 dark:text-neutral-400">
            Hover or focus any card. The gradient glides across and the surface lifts toward you.
          </p>
        </div>

        <div className="grid gap-6 sm:grid-cols-2 lg:grid-cols-3">
          {PLANS.map((plan) => {
            const isActive = active === plan.id;
            return (
              <article
                key={plan.id}
                onMouseEnter={() => setActive(plan.id)}
                onMouseLeave={() => setActive((cur) => (cur === plan.id ? null : cur))}
                className="hcgs-card group relative overflow-hidden rounded-2xl border border-neutral-200 bg-white shadow-sm hover:-translate-y-2 hover:shadow-2xl hover:shadow-indigo-500/10 focus-within:-translate-y-2 focus-within:shadow-2xl dark:border-neutral-800 dark:bg-neutral-900 dark:hover:shadow-indigo-500/20"
              >
                {/* Idle gradient tint */}
                <div
                  aria-hidden="true"
                  className={`hcgs-grad pointer-events-none absolute inset-0 bg-gradient-to-br ${plan.from} ${plan.to} opacity-40 dark:opacity-[0.14]`}
                />
                {/* Vivid gradient revealed on hover/focus */}
                <div
                  aria-hidden="true"
                  className={`hcgs-grad pointer-events-none absolute inset-0 bg-gradient-to-br ${plan.hoverFrom} ${plan.hoverTo} opacity-0 group-hover:opacity-100 group-focus-within:opacity-100 dark:group-hover:opacity-90 dark:group-focus-within:opacity-90`}
                />
                {/* Sheen sweep */}
                {isActive && (
                  <div aria-hidden="true" className="pointer-events-none absolute inset-0 overflow-hidden">
                    <div className="hcgs-sheen absolute inset-y-0 -left-1/3 w-1/3 bg-gradient-to-r from-transparent via-white/60 to-transparent" />
                  </div>
                )}

                <div className="relative flex h-full flex-col p-7">
                  <div className="flex items-baseline justify-between">
                    <h3 className="text-lg font-semibold text-neutral-900 transition-colors duration-300 group-hover:text-white group-focus-within:text-white dark:text-white">
                      {plan.name}
                    </h3>
                    <div className="text-right">
                      <span className="text-2xl font-bold text-neutral-900 transition-colors duration-300 group-hover:text-white group-focus-within:text-white dark:text-white">
                        {plan.price}
                      </span>
                      <span className="text-sm text-neutral-500 transition-colors duration-300 group-hover:text-white/80 group-focus-within:text-white/80 dark:text-neutral-400">
                        {plan.period}
                      </span>
                    </div>
                  </div>

                  <p className="mt-2 text-sm leading-relaxed text-neutral-600 transition-colors duration-300 group-hover:text-white/90 group-focus-within:text-white/90 dark:text-neutral-400">
                    {plan.tagline}
                  </p>

                  <ul className="mt-6 space-y-3">
                    {plan.features.map((feature) => (
                      <li
                        key={feature}
                        className="flex items-start gap-2.5 text-sm text-neutral-700 transition-colors duration-300 group-hover:text-white group-focus-within:text-white dark:text-neutral-300"
                      >
                        <svg
                          viewBox="0 0 20 20"
                          fill="none"
                          aria-hidden="true"
                          className={`mt-0.5 h-4 w-4 flex-none ${plan.accent} transition-colors duration-300 group-hover:text-white group-focus-within:text-white`}
                        >
                          <path
                            d="M4 10.5l3.5 3.5L16 5.5"
                            stroke="currentColor"
                            strokeWidth="2"
                            strokeLinecap="round"
                            strokeLinejoin="round"
                          />
                        </svg>
                        <span>{feature}</span>
                      </li>
                    ))}
                  </ul>

                  <div className="mt-8 pt-2">
                    <a
                      href="#get-started"
                      className="inline-flex w-full items-center justify-center rounded-xl bg-neutral-900 px-4 py-2.5 text-sm font-medium text-white outline-none ring-offset-2 transition-all duration-300 focus-visible:ring-2 focus-visible:ring-indigo-500 group-hover:bg-white group-hover:text-neutral-900 group-focus-within:bg-white group-focus-within:text-neutral-900 dark:bg-white dark:text-neutral-900 dark:ring-offset-neutral-900 dark:group-hover:bg-neutral-900 dark:group-hover:text-white dark:group-focus-within:bg-neutral-900 dark:group-focus-within:text-white"
                    >
                      Choose {plan.name}
                      <svg
                        viewBox="0 0 20 20"
                        fill="none"
                        aria-hidden="true"
                        className="ml-1.5 h-4 w-4 transition-transform duration-300 group-hover:translate-x-0.5"
                      >
                        <path
                          d="M4 10h11m0 0l-4-4m4 4l-4 4"
                          stroke="currentColor"
                          strokeWidth="2"
                          strokeLinecap="round"
                          strokeLinejoin="round"
                        />
                      </svg>
                    </a>
                  </div>
                </div>
              </article>
            );
          })}
        </div>
      </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 →