Web InnoventixFreeCode

Glass Button

Original · free

Glassmorphic buttons over a gradient backdrop.

byWeb InnoventixReact + Tailwind
btnglassbuttons
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/btn-glass.json
btn-glass.tsx
"use client";

import { useState, type ButtonHTMLAttributes, type ReactNode } from "react";
import { useReducedMotion } from "motion/react";

type Variant = "frost" | "tint" | "outline";
type Size = "sm" | "md" | "lg";

const sizeMap: Record<Size, string> = {
  sm: "h-9 px-4 text-sm gap-1.5 rounded-xl",
  md: "h-11 px-5 text-sm gap-2 rounded-2xl",
  lg: "h-13 px-7 text-base gap-2.5 rounded-2xl",
};

const base =
  "relative inline-flex select-none items-center justify-center font-medium tracking-tight " +
  "backdrop-blur-xl transition-[transform,box-shadow,background-color,border-color] duration-200 " +
  "will-change-transform active:scale-[0.97] " +
  "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/70 focus-visible:ring-offset-2 " +
  "focus-visible:ring-offset-transparent disabled:pointer-events-none disabled:opacity-45 " +
  "border shadow-[0_8px_30px_-12px_rgba(0,0,0,0.5)]";

const variantMap: Record<Variant, string> = {
  frost:
    "bg-white/15 border-white/30 text-white hover:bg-white/25 " +
    "shadow-[inset_0_1px_0_0_rgba(255,255,255,0.35),0_8px_30px_-12px_rgba(0,0,0,0.5)]",
  tint:
    "bg-indigo-400/25 border-indigo-200/40 text-white hover:bg-indigo-400/35 " +
    "shadow-[inset_0_1px_0_0_rgba(255,255,255,0.3),0_10px_34px_-14px_rgba(79,70,229,0.7)]",
  outline:
    "bg-white/5 border-white/40 text-white hover:bg-white/15 " +
    "shadow-[inset_0_1px_0_0_rgba(255,255,255,0.2),0_6px_24px_-14px_rgba(0,0,0,0.5)]",
};

function GlassButton({
  children,
  variant = "frost",
  size = "md",
  className = "",
  ...rest
}: {
  children: ReactNode;
  variant?: Variant;
  size?: Size;
  className?: string;
} & ButtonHTMLAttributes<HTMLButtonElement>) {
  return (
    <button
      className={`btnglass-sheen ${base} ${variantMap[variant]} ${sizeMap[size]} ${className}`}
      {...rest}
    >
      {children}
    </button>
  );
}

function IconArrow() {
  return (
    <svg
      viewBox="0 0 24 24"
      className="h-4 w-4"
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M5 12h14" />
      <path d="m13 6 6 6-6 6" />
    </svg>
  );
}

function IconSpinner() {
  return (
    <svg viewBox="0 0 24 24" className="btnglass-spin h-4 w-4" fill="none" aria-hidden="true">
      <circle cx="12" cy="12" r="9" stroke="currentColor" strokeOpacity="0.3" strokeWidth="2.5" />
      <path
        d="M21 12a9 9 0 0 0-9-9"
        stroke="currentColor"
        strokeWidth="2.5"
        strokeLinecap="round"
      />
    </svg>
  );
}

function IconHeart({ filled }: { filled: boolean }) {
  return (
    <svg
      viewBox="0 0 24 24"
      className="h-4 w-4"
      fill={filled ? "currentColor" : "none"}
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="M20.8 4.6a5.5 5.5 0 0 0-7.8 0L12 5.6l-1-1a5.5 5.5 0 1 0-7.8 7.8l1 1L12 21l7.8-7.6 1-1a5.5 5.5 0 0 0 0-7.8Z" />
    </svg>
  );
}

function IconCheck() {
  return (
    <svg
      viewBox="0 0 24 24"
      className="h-4 w-4"
      fill="none"
      stroke="currentColor"
      strokeWidth={2.5}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <path d="m20 6-11 11-5-5" />
    </svg>
  );
}

function IconCopy() {
  return (
    <svg
      viewBox="0 0 24 24"
      className="h-4 w-4"
      fill="none"
      stroke="currentColor"
      strokeWidth={2}
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
    >
      <rect x="9" y="9" width="11" height="11" rx="2.5" />
      <path d="M5 15V6a2 2 0 0 1 2-2h9" />
    </svg>
  );
}

function IconGoogle() {
  return (
    <svg viewBox="0 0 24 24" className="h-4 w-4" aria-hidden="true">
      <path
        fill="#EA4335"
        d="M12 10.2v3.9h5.5c-.24 1.5-1.68 4.4-5.5 4.4A6.5 6.5 0 1 1 16.4 6.9l2.7-2.6A10 10 0 1 0 22 12c0-.66-.07-1.2-.17-1.8H12Z"
      />
    </svg>
  );
}

function IconPlay() {
  return (
    <svg viewBox="0 0 24 24" className="h-4 w-4" fill="currentColor" aria-hidden="true">
      <path d="M8 5.5v13a1 1 0 0 0 1.5.87l11-6.5a1 1 0 0 0 0-1.74l-11-6.5A1 1 0 0 0 8 5.5Z" />
    </svg>
  );
}

export default function BtnGlass() {
  const reduce = useReducedMotion();
  const blob = reduce ? "" : "btnglass-blob";

  const [liked, setLiked] = useState(false);
  const [likeCount, setLikeCount] = useState(248);
  const [copied, setCopied] = useState(false);
  const [loading, setLoading] = useState(false);
  const [subscribed, setSubscribed] = useState(false);

  const toggleLike = () => {
    setLiked((v) => {
      setLikeCount((c) => c + (v ? -1 : 1));
      return !v;
    });
  };

  const copyCode = async () => {
    try {
      await navigator.clipboard.writeText("INNOVENTIX25");
      setCopied(true);
      window.setTimeout(() => setCopied(false), 1800);
    } catch {
      setCopied(false);
    }
  };

  const runLoading = () => {
    if (loading) return;
    setLoading(true);
    window.setTimeout(() => setLoading(false), 1900);
  };

  return (
    <section className="relative w-full overflow-hidden px-6 py-20 sm:py-24">
      <style>{`
        @keyframes btnglass-sheen-move {
          0% { transform: translateX(-130%) skewX(-18deg); }
          60%, 100% { transform: translateX(230%) skewX(-18deg); }
        }
        @keyframes btnglass-spin-rot {
          to { transform: rotate(360deg); }
        }
        @keyframes btnglass-blob {
          0%, 100% { transform: translate(0, 0) scale(1); }
          33% { transform: translate(6%, -5%) scale(1.12); }
          66% { transform: translate(-5%, 4%) scale(0.94); }
        }
        .btnglass-sheen { overflow: hidden; }
        .btnglass-sheen::after {
          content: "";
          position: absolute;
          top: 0; bottom: 0; left: 0;
          width: 45%;
          background: linear-gradient(90deg, transparent, rgba(255,255,255,0.55), transparent);
          transform: translateX(-130%) skewX(-18deg);
          pointer-events: none;
          opacity: 0;
        }
        .btnglass-sheen:hover::after {
          opacity: 1;
          animation: btnglass-sheen-move 1.1s ease-in-out;
        }
        .btnglass-spin { animation: btnglass-spin-rot 0.8s linear infinite; }
        .btnglass-blob { animation: btnglass-blob 18s ease-in-out infinite; }
        @media (prefers-reduced-motion: reduce) {
          .btnglass-sheen:hover::after { animation: none; opacity: 0; }
          .btnglass-spin { animation: none; }
          .btnglass-blob { animation: none; }
        }
      `}</style>

      {/* Gradient backdrop */}
      <div className="absolute inset-0 -z-10 bg-gradient-to-br from-indigo-600 via-violet-600 to-sky-500 dark:from-indigo-900 dark:via-violet-900 dark:to-slate-950" />
      <div
        className={`${blob} absolute -left-24 top-8 -z-10 h-72 w-72 rounded-full bg-rose-400/40 blur-3xl dark:bg-rose-500/25`}
        style={{ animationDelay: "-4s" }}
        aria-hidden="true"
      />
      <div
        className={`${blob} absolute -right-16 bottom-0 -z-10 h-80 w-80 rounded-full bg-emerald-300/40 blur-3xl dark:bg-emerald-500/20`}
        aria-hidden="true"
      />
      <div
        className={`${blob} absolute right-1/3 top-1/4 -z-10 h-64 w-64 rounded-full bg-amber-300/30 blur-3xl dark:bg-amber-400/15`}
        style={{ animationDelay: "-9s" }}
        aria-hidden="true"
      />

      <div className="mx-auto max-w-3xl">
        <div className="rounded-3xl border border-white/20 bg-white/10 p-8 shadow-2xl backdrop-blur-2xl sm:p-10">
          <header className="mb-8">
            <p className="text-xs font-semibold uppercase tracking-[0.2em] text-white/70">
              FreeCode / Buttons
            </p>
            <h2 className="mt-2 text-2xl font-semibold tracking-tight text-white sm:text-3xl">
              Glass buttons
            </h2>
            <p className="mt-2 text-sm text-white/70">
              Frosted, tactile controls that layer over any gradient.
            </p>
          </header>

          {/* Variants */}
          <div className="mb-8">
            <p className="mb-3 text-xs font-medium uppercase tracking-wider text-white/60">
              Variants
            </p>
            <div className="flex flex-wrap items-center gap-3">
              <GlassButton variant="frost">
                Get started
                <IconArrow />
              </GlassButton>
              <GlassButton variant="tint">
                <IconPlay />
                Watch demo
              </GlassButton>
              <GlassButton variant="outline">Learn more</GlassButton>
            </div>
          </div>

          {/* Sizes */}
          <div className="mb-8">
            <p className="mb-3 text-xs font-medium uppercase tracking-wider text-white/60">
              Sizes
            </p>
            <div className="flex flex-wrap items-center gap-3">
              <GlassButton variant="frost" size="sm">
                Small
              </GlassButton>
              <GlassButton variant="frost" size="md">
                Medium
              </GlassButton>
              <GlassButton variant="frost" size="lg">
                Large
              </GlassButton>
            </div>
          </div>

          {/* Interactive */}
          <div className="mb-8">
            <p className="mb-3 text-xs font-medium uppercase tracking-wider text-white/60">
              Interactive
            </p>
            <div className="flex flex-wrap items-center gap-3">
              <GlassButton
                variant={liked ? "tint" : "frost"}
                onClick={toggleLike}
                aria-pressed={liked}
                aria-label={liked ? "Remove like" : "Add like"}
              >
                <span className={liked ? "text-rose-200" : "text-white"}>
                  <IconHeart filled={liked} />
                </span>
                {likeCount.toLocaleString()}
              </GlassButton>

              <GlassButton variant="frost" onClick={copyCode} aria-live="polite">
                {copied ? (
                  <span className="text-emerald-200">
                    <IconCheck />
                  </span>
                ) : (
                  <IconCopy />
                )}
                {copied ? "Copied" : "INNOVENTIX25"}
              </GlassButton>

              <GlassButton
                variant="tint"
                onClick={runLoading}
                disabled={loading}
                aria-busy={loading}
              >
                {loading ? <IconSpinner /> : <IconArrow />}
                {loading ? "Deploying" : "Deploy"}
              </GlassButton>
            </div>
          </div>

          {/* Toggle + social + disabled */}
          <div>
            <p className="mb-3 text-xs font-medium uppercase tracking-wider text-white/60">
              States
            </p>
            <div className="flex flex-wrap items-center gap-3">
              <GlassButton
                variant={subscribed ? "tint" : "outline"}
                onClick={() => setSubscribed((v) => !v)}
                aria-pressed={subscribed}
              >
                {subscribed ? (
                  <>
                    <span className="text-emerald-200">
                      <IconCheck />
                    </span>
                    Subscribed
                  </>
                ) : (
                  "Subscribe"
                )}
              </GlassButton>

              <GlassButton variant="frost">
                <IconGoogle />
                Sign in with Google
              </GlassButton>

              <GlassButton
                variant="frost"
                aria-label="Next"
                size="md"
                className="!px-0 w-11"
              >
                <IconArrow />
              </GlassButton>

              <GlassButton variant="outline" disabled>
                Unavailable
              </GlassButton>
            </div>
          </div>
        </div>
      </div>
    </section>
  );
}

Dependencies

motion

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 →