Glassmorphic CTAReact + Tailwind + Framer Motion

Frosted Gel Dual-Pill Download Button

Dual-pill split CTA button featuring an elevated ceramic primary action, frosted glass cloud download tile, optical refraction rings, and volumetric blue underglow flare.

preview.tsx
Hover or click pill / cloud icon

Customizer

PRESETS & VARIATIONS

All Variants Preview

Hover & click each variant to experience frosted gel optical effects

Figma Electric Bluetheme="figma-blue"
1:1 Figma ceramic pill with volumetric blue flare
Cyber Violettheme="cyber-violet"
Ultraviolet refraction glow with tinted frosted gel
Emerald Matrixtheme="emerald-matrix"
Cyberpunk reactor green glow with translucent glass tile
Magma Orangetheme="magma-orange"
UI Pirate signature brand magma orange illumination
Dark Obsidiantheme="dark-obsidian"
Stealth midnight pill with smoked dark glass cloud tile
Titanium Goldtheme="titanium-gold"
Luxury warm gold underglow with champagne ceramic pill

Installation & Setup

Install the required dependencies for spring animations and styling:

npm install framer-motion clsx

Code & Integration

FrostedGelDownloadButton.tsx• Production Ready
"use client";

import React, { useState } from "react";
import { motion } from "framer-motion";

export type FrostedGelTheme =
  | "figma-blue"
  | "cyber-violet"
  | "emerald-matrix"
  | "magma-orange"
  | "dark-obsidian"
  | "titanium-gold";

export type FrostedGelStateMode = "interactive" | "standerd" | "hover";
export type FrostedGelSize = "xs" | "sm" | "md" | "lg" | "xl";

export interface FrostedGelButtonProps {
  label?: string;
  stateMode?: FrostedGelStateMode;
  theme?: FrostedGelTheme;
  size?: FrostedGelSize;
  showCables?: boolean;
  onDownloadClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
  onIconClick?: (e: React.MouseEvent<HTMLButtonElement>) => void;
  disabled?: boolean;
  className?: string;
}

export function FrostedGelDownloadButton({
  label = "Download now",
  stateMode = "interactive",
  theme = "figma-blue",
  size = "md",
  showCables = true,
  onDownloadClick,
  onIconClick,
  disabled = false,
  className = "",
}: FrostedGelButtonProps) {
  const [isHovered, setIsHovered] = useState(false);
  const [isPressed, setIsPressed] = useState(false);

  const scale = size === "sm" ? 0.8 : size === "lg" ? 1.2 : 1;
  const isVisualHover = stateMode === "hover" || (stateMode === "interactive" && isHovered);

  return (
    <div
      className={`relative select-none flex items-center justify-center ${className}`}
      style={{ width: `${560 * scale}px`, height: `${240 * scale}px` }}
      onMouseEnter={() => setIsHovered(true)}
      onMouseLeave={() => {
        setIsHovered(false);
        setIsPressed(false);
      }}
    >
      <div
        className="relative w-[560px] h-[240px] flex items-center justify-center flex-none transition-transform duration-300"
        style={{ transform: `scale(${scale})` }}
      >
        {/* Volumetric Underglow Flare */}
        <div
          className={`absolute w-[440px] h-[100px] rounded-full blur-[32px] transition-opacity duration-400 pointer-events-none ${
            isVisualHover ? "opacity-100 scale-105" : "opacity-35"
          }`}
          style={{ background: "rgba(0, 123, 254, 0.35)" }}
        />

        {/* Dual-Pill Assembly */}
        <div className="relative flex items-center gap-3">
          {/* Ceramic Pill */}
          <motion.button
            type="button"
            disabled={disabled}
            onClick={onDownloadClick}
            onMouseDown={() => setIsPressed(true)}
            onMouseUp={() => setIsPressed(false)}
            animate={{
              y: isPressed ? 2 : isVisualHover ? -4 : 0,
            }}
            transition={{ type: "spring", stiffness: 450, damping: 25 }}
            className="relative px-8 py-4 rounded-[16.5px] bg-[#F9F9F9] border border-white/90 shadow-[0px_1px_0px_#002AFE,0px_8px_16px_rgba(0,42,254,0.18),inset_0px_3.3px_0px_1.2px_white] text-[#2626FF] font-bold text-sm flex items-center gap-2 cursor-pointer focus:outline-none"
          >
            <span>{label}</span>
          </motion.button>

          {/* Frosted Glass Gel Tile */}
          <motion.button
            type="button"
            disabled={disabled}
            onClick={onIconClick}
            animate={{
              y: isPressed ? 2 : isVisualHover ? -4 : 0,
            }}
            transition={{ type: "spring", stiffness: 450, damping: 25 }}
            className="relative w-[60px] h-[54px] rounded-[16.5px] bg-white/40 backdrop-blur-md border border-white/70 shadow-[0px_8px_18px_rgba(0,42,254,0.25)] flex items-center justify-center text-[#2626FF] cursor-pointer focus:outline-none"
          >
            <svg className="w-5 h-5" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2">
              <path strokeLinecap="round" strokeLinejoin="round" d="M3 15a4 4 0 004 4h9a5 5 0 10-.1-9.999 5.002 5.002 0 00-9.78 2.096A4.001 4.001 0 003 15z" />
            </svg>
          </motion.button>
        </div>
      </div>
    </div>
  );
}

Component API Reference

PropTypeDefaultDescription
labelstring"Download now"Text displayed on the primary ceramic pill
themeFrostedGelTheme"figma-blue"Color scheme preset (Electric Blue, Ultraviolet, Emerald, etc.)
size"xs" | "sm" | "md" | "lg" | "xl""md"Physical scaling multiplier
iconFrostedGelIcon"cloud-download"Icon rendered in the frosted gel tile (cloud-download, download, arrow-down, upload, folder, package, save)
onDownloadClick(e) => voidundefinedCallback fired when the pill button is clicked
onIconClick(e) => voidundefinedCallback fired when the frosted glass cloud tile is clicked

Personalized Help

Struggling with frosted glass CTA buttons or component libraries? Let's talk about your product.

UI Pirate is a product design & development agency trusted by 50+ SaaS founders and enterprise teams across the US, UK & beyond. Tell us what you need.