Metal Button
A beautiful, customizable metal button component with tactile feedback.
Installation
npx shadcn@latest add https://fluxbuttons.vercel.app/r/metal-button.json"use client";
import React from "react";
import { cn } from "@/lib/utils";
type ColorVariant =
| "default"
| "primary"
| "success"
| "error"
| "gold"
| "bronze";
interface MetalButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
variant?: ColorVariant;
}
const colorVariants: Record<
ColorVariant,
{
outer: string;
inner: string;
button: string;
textColor: string;
textShadow: string;
}
> = {
default: {
outer: "bg-gradient-to-b from-[#000] to-[#A0A0A0]",
inner: "bg-gradient-to-b from-[#FAFAFA] via-[#3E3E3E] to-[#E5E5E5]",
button: "bg-gradient-to-b from-[#B9B9B9] to-[#969696]",
textColor: "text-white",
textShadow: "[text-shadow:_0_-1px_0_rgb(80_80_80_/_100%)]",
},
primary: {
outer: "bg-gradient-to-b from-[#0051B4] to-[#90C2FF]",
inner: "bg-gradient-to-b from-[#C4EBFF] via-[#0B3F89] to-[#A6DDFB]",
button: "bg-gradient-to-b from-[#96C6EA] to-[#2D7CCA]",
textColor: "text-[#FFF7F0]",
textShadow: "[text-shadow:_0_-1px_0_rgb(30_58_138_/_100%)]",
},
success: {
outer: "bg-gradient-to-b from-[#005A43] to-[#7CCB9B]",
inner: "bg-gradient-to-b from-[#E5F8F0] via-[#00352F] to-[#D1F0E6]",
button: "bg-gradient-to-b from-[#9ADBC8] to-[#3E8F7C]",
textColor: "text-[#FFF7F0]",
textShadow: "[text-shadow:_0_-1px_0_rgb(6_78_59_/_100%)]",
},
error: {
outer: "bg-gradient-to-b from-[#5A0000] to-[#FFAEB0]",
inner: "bg-gradient-to-b from-[#FFDEDE] via-[#680002] to-[#FFE9E9]",
button: "bg-gradient-to-b from-[#F08D8F] to-[#A45253]",
textColor: "text-[#FFF7F0]",
textShadow: "[text-shadow:_0_-1px_0_rgb(146_64_14_/_100%)]",
},
gold: {
outer: "bg-gradient-to-b from-[#917100] to-[#EAD98F]",
inner: "bg-gradient-to-b from-[#FFFDDD] via-[#856807] to-[#FFF1B3]",
button: "bg-gradient-to-b from-[#FFEBA1] to-[#9B873F]",
textColor: "text-[#FFFDE5]",
textShadow: "[text-shadow:_0_-1px_0_rgb(178_140_2_/_100%)]",
},
bronze: {
outer: "bg-gradient-to-b from-[#864813] to-[#E9B486]",
inner: "bg-gradient-to-b from-[#EDC5A1] via-[#5F2D01] to-[#FFDEC1]",
button: "bg-gradient-to-b from-[#FFE3C9] to-[#A36F3D]",
textColor: "text-[#FFF7F0]",
textShadow: "[text-shadow:_0_-1px_0_rgb(124_45_18_/_100%)]",
},
};
const metalButtonVariants = (
variant: ColorVariant = "default",
isPressed: boolean,
isHovered: boolean,
isTouchDevice: boolean,
) => {
const colors = colorVariants[variant];
const transitionStyle = "all 250ms cubic-bezier(0.1, 0.4, 0.2, 1)";
return {
wrapper: cn(
"relative inline-flex transform-gpu rounded-full p-[1.25px] will-change-transform",
colors.outer,
),
wrapperStyle: {
transform: isPressed
? "translateY(2.5px) scale(0.99)"
: "translateY(0) scale(1)",
boxShadow: isPressed
? "0 1px 2px rgba(0, 0, 0, 0.15)"
: isHovered && !isTouchDevice
? "0 4px 12px rgba(0, 0, 0, 0.12)"
: "0 3px 8px rgba(0, 0, 0, 0.08)",
transition: transitionStyle,
transformOrigin: "center center",
},
inner: cn(
"absolute inset-[1px] transform-gpu rounded-full will-change-transform",
colors.inner,
),
innerStyle: {
transition: transitionStyle,
transformOrigin: "center center",
filter:
isHovered && !isPressed && !isTouchDevice ? "brightness(1.05)" : "none",
},
button: cn(
"relative z-10 m-[2.5px] inline-flex h-11 transform-gpu cursor-pointer items-center justify-center overflow-hidden rounded-full px-6 pt-4 pb-5 text-2xl leading-none font-bold will-change-transform outline-none",
colors.button,
colors.textColor,
colors.textShadow,
),
buttonStyle: {
transform: isPressed ? "scale(0.97)" : "scale(1)",
transition: transitionStyle,
transformOrigin: "center center",
filter:
isHovered && !isPressed && !isTouchDevice ? "brightness(1.02)" : "none",
},
};
};
const ShineEffect = ({ isPressed }: { isPressed: boolean }) => {
return (
<div
className={cn(
"pointer-events-none absolute inset-0 z-20 overflow-hidden rounded-full transition-opacity duration-300",
isPressed ? "opacity-20" : "opacity-0",
)}
>
<div className="absolute inset-0 bg-gradient-to-r from-transparent via-gray-100 to-transparent" />
</div>
);
};
export const MetalButton = React.forwardRef<
HTMLButtonElement,
MetalButtonProps
>(({ children, className, variant = "default", ...props }, ref) => {
const [isPressed, setIsPressed] = React.useState(false);
const [isHovered, setIsHovered] = React.useState(false);
const [isTouchDevice, setIsTouchDevice] = React.useState(false);
React.useEffect(() => {
setIsTouchDevice("ontouchstart" in window || navigator.maxTouchPoints > 0);
}, []);
const buttonText = children || "Button";
const variants = metalButtonVariants(
variant,
isPressed,
isHovered,
isTouchDevice,
);
const handleInternalMouseDown = () => {
setIsPressed(true);
};
const handleInternalMouseUp = () => {
setIsPressed(false);
};
const handleInternalMouseLeave = () => {
setIsPressed(false);
setIsHovered(false);
};
const handleInternalMouseEnter = () => {
if (!isTouchDevice) {
setIsHovered(true);
}
};
const handleInternalTouchStart = () => {
setIsPressed(true);
};
const handleInternalTouchEnd = () => {
setIsPressed(false);
};
const handleInternalTouchCancel = () => {
setIsPressed(false);
};
return (
<div className={variants.wrapper} style={variants.wrapperStyle}>
<div className={variants.inner} style={variants.innerStyle}></div>
<button
ref={ref}
className={cn(variants.button, className)}
style={variants.buttonStyle}
{...props}
onMouseDown={handleInternalMouseDown}
onMouseUp={handleInternalMouseUp}
onMouseLeave={handleInternalMouseLeave}
onMouseEnter={handleInternalMouseEnter}
onTouchStart={handleInternalTouchStart}
onTouchEnd={handleInternalTouchEnd}
onTouchCancel={handleInternalTouchCancel}
>
<ShineEffect isPressed={isPressed} />
{buttonText}
{isHovered && !isPressed && !isTouchDevice && (
<div className="pointer-events-none absolute inset-0 rounded-full bg-gradient-to-t from-transparent to-white/5" />
)}
</button>
</div>
);
});
MetalButton.displayName = "MetalButton";Usage
import { MetalButton } from "@/components/ui/metal-button";
export function MetalButtonDemo() {
return <MetalButton variant="default">Button</MetalButton>;
}Examples
Primary
import { MetalButton } from "@/components/ui/metal-button";
export function MetalButtonPrimary() {
return <MetalButton variant="primary">Primary</MetalButton>;
}Success
import { MetalButton } from "@/components/ui/metal-button";
export function MetalButtonPrimary() {
return <MetalButton variant="success">Primary</MetalButton>;
}Error
import { MetalButton } from "@/components/ui/metal-button";
export function MetalButtonPrimary() {
return <MetalButton variant="error">Primary</MetalButton>;
}Gold
import { MetalButton } from "@/components/ui/metal-button";
export function MetalButtonPrimary() {
return <MetalButton variant="gold">Primary</MetalButton>;
}Bronze
import { MetalButton } from "@/components/ui/metal-button";
export function MetalButtonPrimary() {
return <MetalButton variant="bronze">Primary</MetalButton>;
}API Reference
Glass Button
The Fold Button component is a wrapper around the button element that adds a variety of styles and functionality.
Prop
Type