Glossy Button
new
A tactile gradient button — stacked fill + hairline gradients with a three-part shadow for real depth. Dark and light variants, elegant hover/press states.
hover to lift · press to sink · tab to focus
Usage
example.tsx
import { GlossButton } from "~/components/buttons/gloss-button";<GlossButton variant="dark" onClick={accept}>Accept</GlossButton><GlossButton variant="light" onClick={reject}>Reject</GlossButton>
How the depth works
The button stacks two gradients in one background: the fill is clipped to padding-box, and a lighter hairline gradient is clipped to border-box — so a 1px transparent border reveals a gradient stroke that catches light at the top, exactly like the Figma layers.
Depth is a three-part box-shadow: a soft 0 2px 4px ambient shadow lifts it off the page, a crisp 0 0 0 1px ring sharpens the edge, and an inset highlight adds a top sheen. Hover raises the lift and brightness; active sinks and dims it.
Tailwind
GlossButton.tsx
// The gradients need two background layers with different clips, so they// live in `style`; Tailwind handles shape, shadow, and every state.<buttonstyle={{background:"linear-gradient(180deg,#201E25,#323137) padding-box," +"linear-gradient(180deg,#4B4951,#313036) border-box",}}className="inline-flex items-center justify-center rounded-[18px]border border-transparent px-9 py-3.5 text-xl font-semibold text-zinc-100shadow-[0_2px_4px_rgba(0,0,0,0.1),0_0_0_1px_#0D0D0D,inset_0_1px_0_rgba(255,255,255,0.06)]transition-[transform,box-shadow,filter] duration-150 ease-outhover:-translate-y-px hover:brightness-110hover:shadow-[0_6px_16px_rgba(0,0,0,0.22),0_0_0_1px_#0D0D0D,inset_0_1px_0_rgba(255,255,255,0.09)]active:translate-y-0 active:scale-[0.985] active:brightness-95focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-blue-500">Accept</button>
Plain CSS (no Tailwind)
gloss-button.css
.btn-gloss {--lift: 0px;border: 1px solid transparent;border-radius: 18px;padding: 0.85rem 2.25rem;font: 600 1.25rem/1 system-ui, sans-serif;transform: translateY(var(--lift));transition:transform 0.16s cubic-bezier(0.2, 0.7, 0.2, 1),box-shadow 0.16s ease,filter 0.16s ease;}/* Dark — "Accept" */.btn-gloss--dark {color: #f4f4f5;background:linear-gradient(180deg, #201e25, #323137) padding-box, /* fill */linear-gradient(180deg, #4b4951, #313036) border-box; /* stroke */box-shadow:0 2px 4px rgba(0, 0, 0, 0.1), /* ambient lift */0 0 0 1px #0d0d0d, /* crisp ring */inset 0 1px 0 rgba(255, 255, 255, 0.06); /* top sheen */}.btn-gloss--dark:hover { --lift: -1px; filter: brightness(1.08); }.btn-gloss--dark:active { transform: scale(0.985); filter: brightness(0.96); }/* Light — "Reject" */.btn-gloss--light {color: #1b1b1f;background:linear-gradient(rgba(227, 227, 227, 0.8), rgba(227, 227, 227, 0.8)) padding-box,linear-gradient(180deg, #fdfdfd, rgba(241, 241, 241, 0)) border-box;box-shadow:0 2px 4px rgba(0, 0, 0, 0.1),0 0 0 1px rgba(0, 0, 0, 0.16),inset 0 1px 0 rgba(255, 255, 255, 0.7);}
Props
| prop | type | default | description |
|---|---|---|---|
| variant | "dark" | "light" | "dark" | Filled dark button, or the light secondary. |
| children | ReactNode | — | Button label. |
| disabled | boolean | false | Dims and disables; cancels hover/press motion. |
| …props | ButtonHTMLAttributes | — | Any native button prop (onClick, type, aria-*). |