Menu

Glassmorphic Pricing Card

A premium glass pricing card with smoother hover physics, metadata chips, and a sharpened blade-style spotlight for the featured tier.

Preview

Live interactive preview.

Installation

Add this component to your project using the CLI:

terminal
npx -y vui-registry-cli-v1@latest add glassmorphic-pricing-card

Source Code

glassmorphic-pricing-card.tsx
"use client";

import React from "react";
import { motion, useMotionTemplate, useMotionValue, useSpring } from "framer-motion";
import { ArrowRight, Check } from "lucide-react";
import { cn } from "@/lib/utils";
import { ReactIcon, NextjsIcon, TailwindCssIcon as TailwindIcon } from "@/components/icons/tech-svgs";

const pricingPlans = [
  {
    name: "React Starter",
    icon: ReactIcon,
    price: "$19",
    description: "Perfect for indie hackers and small React projects.",
    features: [
      "Access to 50+ basic components",
      "Community support",
      "Personal project license",
      "Basic templates"
    ],
    popular: false
  },
  {
    name: "Next.js Pro",
    icon: NextjsIcon,
    price: "$49",
    description: "Everything you need to build Next.js apps at lightspeed.",
    features: [
      "Access to all 150+ components",
      "Figma design files included",
      "Commercial project license",
      "Priority email support",
      "Free updates for 1 year"
    ],
    popular: true
  },
  {
    name: "Tailwind Team",
    icon: TailwindIcon,
    price: "$99",
    description: "For agencies and teams building scalable UIs.",
    features: [
      "Everything in Pro",
      "Unlimited team members",
      "Custom component requests",
      "Dedicated Slack channel",
      "Lifetime updates"
    ],
    popular: false
  }
];

function PricingCard({ plan, index }: { plan: typeof pricingPlans[0]; index: number }) {
  const mouseX = useMotionValue(0);
  const mouseY = useMotionValue(0);

  const handleMouseMove = (e: React.MouseEvent<HTMLDivElement>) => {
    const { left, top } = e.currentTarget.getBoundingClientRect();
    mouseX.set(e.clientX - left);
    mouseY.set(e.clientY - top);
  };

  const springX = useSpring(mouseX, { stiffness: 300, damping: 35 });
  const springY = useSpring(mouseY, { stiffness: 300, damping: 35 });

  const spotlight = useMotionTemplate`radial-gradient(320px circle at ${springX}px ${springY}px, rgba(var(--foreground-rgb), 0.05), transparent 80%)`;

  return (
    <motion.div
      initial={{ opacity: 0, y: 20 }}
      animate={{ opacity: 1, y: 0 }}
      transition={{ duration: 0.5, delay: index * 0.1, ease: [0.21, 0.47, 0.32, 0.98] }}
      onMouseMove={handleMouseMove}
      className={cn(
        "group relative w-full max-w-[275px] sm:max-w-[285px] lg:max-w-[295px] shrink rounded-2xl cursor-pointer transition-transform duration-500 hover:-translate-y-1",
        plan.popular ? "z-10" : "z-0"
      )}
    >
      {/* 1. Base Border that Fades Seamlessly into Bottom Transparency */}
      <div
        className="absolute inset-0 rounded-2xl border border-foreground/20 transition-colors duration-500 group-hover:border-foreground/35 pointer-events-none z-10"
        style={{
          WebkitMaskImage: "linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0.5) 50%, rgba(0,0,0,0) 95%)",
          maskImage: "linear-gradient(to bottom, rgba(0,0,0,1) 0%, rgba(0,0,0,0.5) 50%, rgba(0,0,0,0) 95%)",
        }}
      />

      {/* 2. Curved 1px Masked Razor Top Rim */}
      <div
        className={cn(
          "absolute inset-0 rounded-2xl p-[1px] pointer-events-none z-20 transition-opacity duration-500",
          plan.popular ? "opacity-100" : "opacity-0 group-hover:opacity-100"
        )}
        style={{
          background:
            "linear-gradient(180deg, rgba(var(--foreground-rgb), 0.85) 0%, rgba(var(--foreground-rgb), 0.2) 30%, transparent 80%)",
          WebkitMask: "linear-gradient(#fff 0 0) content-box, linear-gradient(#fff 0 0)",
          WebkitMaskComposite: "xor",
          maskComposite: "exclude",
        }}
      />

      {/* 3. Ultra-Sharp Razor Edge Glare Beam */}
      <div
        className={cn(
          "absolute top-0 left-1/2 -translate-x-1/2 h-[1px] pointer-events-none z-30 transition-all duration-500",
          plan.popular
            ? "w-3/4 bg-gradient-to-r from-transparent via-foreground to-transparent opacity-100"
            : "w-1/2 bg-gradient-to-r from-transparent via-foreground/80 to-transparent opacity-0 group-hover:opacity-100"
        )}
      />

      {/* 4. Ambient Soft Backglow (Popular Plan) */}
      {plan.popular && (
        <div className="absolute inset-0 -z-10 rounded-2xl bg-foreground/5 blur-xl transition-all group-hover:bg-foreground/10" />
      )}

      {/* 5. Main Card Surface */}
      <div className="relative flex h-full w-full flex-col overflow-hidden rounded-2xl bg-background/90 backdrop-blur-2xl p-5 sm:p-6 shadow-sm z-0">
        
        {/* Dynamic Hover Spotlight */}
        <motion.div
          className="absolute inset-0 pointer-events-none opacity-0 transition-opacity duration-500 group-hover:opacity-100"
          style={{ background: spotlight }}
        />

        {/* Card Content */}
        <div className="relative z-10 flex flex-1 flex-col">
          <div className="mb-4 flex items-center justify-between">
            <div className="flex items-center gap-2 rounded-full bg-foreground/5 px-2.5 py-1 text-xs font-medium text-foreground/90 border border-foreground/10">
              <plan.icon className="h-3.5 w-3.5 opacity-80" />
              <span>{plan.name}</span>
            </div>
            {plan.popular && (
              <span className="rounded-full bg-foreground text-background px-2 py-0.5 text-[9px] font-semibold uppercase tracking-wider shadow-sm">
                Popular
              </span>
            )}
          </div>

          <div className="mb-1.5 flex items-baseline gap-1">
            <h3 className="text-3xl sm:text-4xl font-semibold tracking-tight text-foreground">
              {plan.price}
            </h3>
            <span className="text-xs sm:text-sm font-medium text-muted-foreground">/mo</span>
          </div>

          <p className="mb-5 min-h-[36px] text-xs sm:text-sm leading-relaxed text-muted-foreground">
            {plan.description}
          </p>

          <div className="mb-6 flex flex-1 flex-col gap-2.5">
            {plan.features.map((feature, i) => (
              <div key={i} className="flex items-center gap-2.5">
                <div className="flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-full bg-foreground/5 text-foreground/70">
                  <Check className="h-3 w-3" strokeWidth={2.5} />
                </div>
                <span className="text-xs sm:text-sm text-foreground/80 leading-snug">{feature}</span>
              </div>
            ))}
          </div>

          <button
            className={cn(
              "group/btn relative flex h-10 w-full items-center justify-center gap-2 rounded-xl text-xs sm:text-sm font-semibold transition-all duration-300 overflow-hidden",
              plan.popular
                ? "bg-foreground text-background shadow-md hover:shadow-lg hover:opacity-90"
                : "bg-foreground/5 text-foreground hover:bg-foreground/10"
            )}
          >
            <span className="relative z-10 flex items-center gap-2">
              Get Started
              <ArrowRight className="h-3.5 w-3.5 transition-transform duration-300 group-hover/btn:translate-x-1" />
            </span>
          </button>
        </div>
      </div>
    </motion.div>
  );
}

export default function GlassmorphicPricing({
  className,
  showHeader = true,
}: {
  className?: string;
  showHeader?: boolean;
}) {
  return (
    <section className={cn("relative flex w-full flex-col items-center justify-center overflow-hidden bg-background py-8 sm:py-10 px-3 sm:px-6", className)}>
      {showHeader && (
        <div className="relative z-10 mx-auto mb-6 sm:mb-8 max-w-2xl text-center px-4">
          <h2 className="mb-2 text-2xl sm:text-3xl lg:text-4xl font-semibold tracking-tight text-foreground">
            Simple, transparent pricing
          </h2>
          <p className="text-xs sm:text-sm text-muted-foreground">
            Choose the perfect plan for your needs. No hidden fees.
          </p>
        </div>
      )}

      <div className="relative z-20 flex w-full max-w-5xl flex-col sm:flex-row items-center sm:items-stretch justify-center gap-3.5 sm:gap-4 lg:gap-5">
        {pricingPlans.map((plan, index) => (
          <PricingCard key={plan.name} plan={plan} index={index} />
        ))}
      </div>
    </section>
  );
}

Dependencies

  • framer-motion: latest
  • lucide-react: latest

Props

Component property reference.

NameTypeDefaultDescription
planObject-The plan configuration including name, price, features, and popularity.
indexnumber0Index of the card for staggered entrance animations.
Context Worth Keeping In Orbit

Most components here are inspired by outstanding libraries and creators in the ecosystem. I don’t claim to be the original author — this is my space for learning, rebuilding, and understanding great work at a deeper level.

I’m still a student of the craft, constantly studying the best and translating what I learn through my own perspective. Every piece reflects curiosity, respect for the community, and small creative touches that feel true to me.

I’ve done my best to credit inspirations properly. If anything is missing or inaccurate, I truly appreciate a message so it can be corrected with care.