Skip to content
← components

Hardware Button

new

A realistic physical button — a gunmetal bezel housing a recessed, illuminated glass panel with embossed text and a colored light bloom. Pure CSS; presses into its housing.

color
arrow

press it — the glass sinks into the housing

Usage

example.tsx
import { HardwareButton } from "~/components/buttons/hardware-button";
<HardwareButton color="#FFD400" onClick={start}>
LETS VENTURE.
</HardwareButton>

How the hardware look works

It's three nested layers. The outer button is the chrome bezel: a steep gray gradient with inset bevels — a bright top edge, a dark bottom and right — so it reads as polished metal lit from above. Its padding is the bezel thickness.

Inside sits a black recessed well with its own deep inset shadow — that's the dark margin the glass appears to float in. Then the glass lens: a dark→bright→dark vertical gradient so it reads as a convex tube catching light through its middle. Two pseudo-elements sell the glass — a curved top sheen and a soft waterline reflection across the lower third — and an outer box-shadow bloom spills its color onto the surface. Press it and the whole thing drops 2px while the glass shadow deepens, sinking into the housing. Everything derives from one --hw-color.

hardware-button.css
/* 1. Chrome bezel — metal gradient, beveled insets, outer bloom */
.hw-btn {
--hw-color: #ffd400;
padding: 12px; /* the bezel thickness */
border-radius: 30px;
background: linear-gradient(156deg, #cfcfd6, #8c8c93 8%, #4a4a50 28%, #232327 60%, #101013);
box-shadow:
0 30px 55px -16px rgba(0,0,0,.9),
0 16px 70px -6px color-mix(in srgb, var(--hw-color) 45%, transparent), /* glow */
inset 0 2px 1px rgba(255,255,255,.7), /* bright top edge */
inset -2px 0 3px rgba(0,0,0,.6),
inset 0 -4px 6px rgba(0,0,0,.75);
}
/* 2. Black recessed well — the dark margin the glass floats in */
.hw-btn__well {
padding: 13px;
border-radius: 20px;
background: linear-gradient(180deg, #050506, #121214);
box-shadow: inset 0 4px 9px rgba(0,0,0,.9), inset 0 0 0 1px rgba(0,0,0,.9);
}
/* 3. Convex glass — dark→bright→dark = a lit tube */
.hw-btn__glass {
border-radius: 13px;
padding: 20px 40px;
background: linear-gradient(180deg,
color-mix(in srgb,var(--hw-color) 72%,#000) 0%, var(--hw-color) 26%,
color-mix(in srgb,var(--hw-color) 52%,#fff) 52%, var(--hw-color) 74%,
color-mix(in srgb,var(--hw-color) 72%,#000) 100%);
box-shadow: 0 0 26px color-mix(in srgb,var(--hw-color) 70%,transparent),
inset 0 1px 0 rgba(255,255,255,.55);
}
.hw-btn__glass::before { /* curved top sheen */
content:""; position:absolute; inset:1px 1px auto 1px; height:50%;
border-radius: 12px 12px 50% 50% / 12px 12px 26px 26px;
background: linear-gradient(180deg, rgba(255,255,255,.6), rgba(255,255,255,.05));
}
.hw-btn__glass::after { /* convex waterline reflection */
content:""; position:absolute; left:7%; right:7%; bottom:13%; height:18%;
border-radius:100%; filter:blur(1.5px);
background: linear-gradient(180deg, rgba(255,255,255,.5), transparent);
}
/* 4. Press it into the housing */
.hw-btn:active { transform: translateY(2px); }
.hw-btn:active .hw-btn__glass { box-shadow: inset 0 6px 12px rgba(0,0,0,.6); }

Props

proptypedefaultdescription
colorstring"#FFD400"Glass + glow color; the whole panel derives from it.
arrowbooleantrueShow the trailing ↗ arrow.
childrenReactNode"LETS VENTURE."Button label.
labelColorstring"#171200"Label / arrow color.
disabledbooleanfalseDims and desaturates; cancels press.
…propsButtonHTMLAttributesAny native button prop (onClick, type, aria-*).

The full implementation, across 2 files. Copy it in or grab the zip. It leans on a cn() class helper and the theme tokens (--primary, --border, …).

components/buttons/hardware-button.tsx
import { ArrowUpRight } from "lucide-react";
import { cn } from "~/lib/utils";
export interface HardwareButtonProps
extends React.ButtonHTMLAttributes<HTMLButtonElement> {
/** Illuminated glass + glow color. The whole panel derives from it. Default #FFD400. */
color?: string;
/** Label / arrow color. Default a near-black for bright panels. */
labelColor?: string;
/** Show the trailing ↗ arrow. Default true. */
arrow?: boolean;
}
/**
* A realistic physical button: a gunmetal bezel housing a recessed, illuminated
* glass panel. The look is pure CSS — a metal gradient frame with beveled inset
* highlights, a radial-gradient "lit glass" insert, a glossy sheen pseudo-
* element, embossed text, and an outer colored bloom. Press it and it sinks
* into the housing. Recolor the whole thing via `color`.
*/
export function HardwareButton({
color,
labelColor,
arrow = true,
children = "LETS VENTURE.",
className,
type = "button",
style,
...props
}: HardwareButtonProps) {
const vars = { ...style } as Record<string, string>;
if (color) vars["--hw-color"] = color;
if (labelColor) vars["--hw-label"] = labelColor;
return (
<button
type={type}
className={cn("hw-btn", className)}
style={vars as React.CSSProperties}
{...props}
>
<span className="hw-btn__well">
<span className="hw-btn__glass">
<span className="hw-btn__label">
{children}
{arrow ? (
<ArrowUpRight className="size-[1.1em]" strokeWidth={2.5} />
) : null}
</span>
</span>
</span>
</button>
);
}
app/styles/app.css
/* ─── Hardware button (illuminated glass + chrome bezel) ───────────── */
/* Three nested layers: polished metal bezel → black recessed well →
convex lit-glass lens floating inside it. */
.hw-btn {
--hw-color: #ffd400;
--hw-light: color-mix(in srgb, var(--hw-color) 52%, #fff);
--hw-dark: color-mix(in srgb, var(--hw-color) 72%, #000);
--hw-label: #171200;
position: relative;
display: inline-flex;
cursor: pointer;
border: none;
padding: 17px;
border-radius: 36px;
/* polished chrome: bright top edge, dark belly, faint bottom rim-light */
background: linear-gradient(
180deg,
#ececf2 0%,
#b6b6bf 5%,
#6e6e77 15%,
#34343a 40%,
#1a1a1e 68%,
#2a2a30 88%,
#131316 100%
);
box-shadow:
0 42px 70px -18px rgba(0, 0, 0, 0.95),
0 10px 20px -6px rgba(0, 0, 0, 0.8),
0 24px 95px -10px color-mix(in srgb, var(--hw-color) 55%, transparent),
inset 0 3px 2px -1px rgba(255, 255, 255, 0.95),
inset 0 1px 0 rgba(255, 255, 255, 0.55),
inset 0 0 0 1px rgba(255, 255, 255, 0.07),
inset 3px 0 4px rgba(255, 255, 255, 0.16),
inset -3px 0 4px rgba(0, 0, 0, 0.65),
inset 0 -5px 7px rgba(0, 0, 0, 0.85);
transition:
transform 0.15s cubic-bezier(0.2, 0.7, 0.2, 1),
box-shadow 0.2s ease,
filter 0.2s ease;
}
/* diagonal chrome streak across the bezel (sits behind the well) */
.hw-btn::before {
content: "";
position: absolute;
inset: 0;
z-index: 0;
border-radius: 36px;
background: linear-gradient(
125deg,
rgba(255, 255, 255, 0.28) 0%,
rgba(255, 255, 255, 0) 24%,
rgba(255, 255, 255, 0) 78%,
rgba(255, 255, 255, 0.06) 100%
);
pointer-events: none;
}
/* black recessed well — gives the dark margin the glass floats inside */
.hw-btn__well {
position: relative;
z-index: 1;
display: flex;
width: 100%;
padding: 15px;
border-radius: 24px;
background: linear-gradient(180deg, #050506, #121214);
box-shadow:
inset 0 5px 11px rgba(0, 0, 0, 0.92),
inset 0 -1px 1px rgba(255, 255, 255, 0.06),
inset 0 0 0 1px rgba(0, 0, 0, 0.9);
}
/* convex glass lens */
.hw-btn__glass {
position: relative;
overflow: hidden;
flex: 1;
display: inline-flex;
align-items: center;
justify-content: center;
padding: 23px 46px;
border-radius: 15px;
/* dark-bright-dark vertical = a convex tube catching light through the middle */
background:
radial-gradient(
120% 85% at 50% 118%,
color-mix(in srgb, var(--hw-color) 32%, #fff) 0%,
transparent 58%
),
linear-gradient(
180deg,
var(--hw-dark) 0%,
var(--hw-color) 26%,
var(--hw-light) 52%,
var(--hw-color) 74%,
var(--hw-dark) 100%
);
box-shadow:
0 0 26px color-mix(in srgb, var(--hw-color) 70%, transparent),
inset 0 1px 0 rgba(255, 255, 255, 0.55),
inset 0 -1px 2px rgba(0, 0, 0, 0.45),
inset 0 0 0 1px rgba(0, 0, 0, 0.3);
}
/* top specular sheen, curved at its lower edge */
.hw-btn__glass::before {
content: "";
position: absolute;
inset: 1px 1px auto 1px;
height: 50%;
border-radius: 12px 12px 50% 50% / 12px 12px 26px 26px;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.6),
rgba(255, 255, 255, 0.05)
);
pointer-events: none;
}
/* convex "waterline" reflection across the lower third */
.hw-btn__glass::after {
content: "";
position: absolute;
left: 7%;
right: 7%;
bottom: 13%;
height: 18%;
border-radius: 100%;
background: linear-gradient(
180deg,
rgba(255, 255, 255, 0.5),
rgba(255, 255, 255, 0)
);
filter: blur(1.5px);
pointer-events: none;
}
.hw-btn__label {
position: relative;
z-index: 1;
display: inline-flex;
align-items: center;
gap: 0.5em;
font-family: var(--font-sans);
font-weight: 700;
font-size: 1.6rem;
letter-spacing: 0.06em;
white-space: nowrap;
color: var(--hw-label);
text-shadow: 0 1px 0 rgba(255, 255, 255, 0.28);
}
.hw-btn:hover {
filter: brightness(1.05);
box-shadow:
0 34px 60px -16px rgba(0, 0, 0, 0.9),
0 20px 90px -4px color-mix(in srgb, var(--hw-color) 60%, transparent),
inset 0 2px 1px rgba(255, 255, 255, 0.75),
inset 0 0 0 1px rgba(255, 255, 255, 0.07),
inset 2px 0 3px rgba(255, 255, 255, 0.16),
inset -2px 0 3px rgba(0, 0, 0, 0.6),
inset 0 -4px 6px rgba(0, 0, 0, 0.75);
}
.hw-btn:active {
transform: translateY(2px);
filter: brightness(0.97);
}
.hw-btn:active .hw-btn__glass {
box-shadow:
0 0 14px color-mix(in srgb, var(--hw-color) 55%, transparent),
inset 0 6px 12px rgba(0, 0, 0, 0.6),
inset 0 0 0 1px rgba(0, 0, 0, 0.4);
}
.hw-btn:focus-visible {
outline: 3px solid color-mix(in srgb, var(--hw-color) 60%, transparent);
outline-offset: 3px;
}
.hw-btn:disabled {
cursor: not-allowed;
filter: grayscale(0.6) brightness(0.6);
}
@media (prefers-reduced-motion: reduce) {
.hw-btn {
transition: none;
}
}