Candy Button
new
A glossy, puffed-up 3D call-to-action — drop shadow, blue edge ring, inset white stroke, and a top sheen. Lifts on hover, presses in on click.
palette
icon
hover to lift · press to sink · tab to focus
Usage
example.tsx
import { Plus } from "lucide-react";import { CandyButton } from "~/components/buttons/candy-button";// Recolor the whole button — gloss, ring, glow, focus — from one prop.<CandyButton icon={<Plus />} color="#296FF0" radius={18} onClick={addMember}>Add team member</CandyButton>
How the puff works
There's no 3D here — just four stacked shadows. A bottom 0 3px 6px drop shadow lifts the button off the page. A 0 0 0 1px ring in the same blue seats the edge so it doesn't look cut out. An inset 1px white stroke is the bright rim, and an inset white blur near the top is the sheen that sells the gloss.
On hover it rises two pixels and the drop shadow grows and tints blue, so it appears to float. On :active it drops a pixel, scales down a hair, and swaps to an inset dark shadow — the light pushes in and it reads as physically pressed.
Tailwind
CandyButton.tsx
<buttonclassName="inline-flex items-center justify-center gap-2 rounded-[18px]px-8 py-4 text-lg font-semibold text-whitebg-[linear-gradient(180deg,#3F7DF4,#296FF0)]shadow-[0_3px_6px_rgba(0,0,0,0.2),0_0_0_1px_#296FF0,inset_0_0_0_1px_rgba(255,255,255,0.4),inset_0_1px_6px_2px_rgba(255,255,255,0.24)]transition-[transform,box-shadow,filter] duration-150 ease-outhover:-translate-y-0.5 hover:brightness-105hover:shadow-[0_8px_18px_rgba(41,111,240,0.35),0_0_0_1px_#296FF0,inset_0_0_0_1px_rgba(255,255,255,0.45),inset_0_1px_6px_2px_rgba(255,255,255,0.3)]active:translate-y-px active:scale-[0.99] active:brightness-95focus-visible:outline-[3px] focus-visible:outline-offset-2 focus-visible:outline-[rgba(41,111,240,0.45)]"><Plus className="size-5" />Add team member</button>
Plain CSS (no Tailwind)
candy-button.css
.btn-candy {--lift: 0px;display: inline-flex;align-items: center;gap: 0.6rem;border: none;color: #fff;border-radius: 18px;padding: 0.95rem 1.9rem;font: 600 1.125rem/1 system-ui, sans-serif;background: linear-gradient(180deg, #3f7df4, #296ff0);box-shadow:0 3px 6px rgba(0, 0, 0, 0.2), /* 1. drop — elevation */0 0 0 1px #296ff0, /* 3. blue ring */inset 0 0 0 1px rgba(255, 255, 255, 0.4), /* stroke: white 40% */inset 0 1px 6px 2px rgba(255, 255, 255, 0.24); /* 2. top sheen */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;}.btn-candy:hover { --lift: -2px; filter: brightness(1.05); } /* rise */.btn-candy:active { transform: translateY(1px) scale(0.99); /* sink */box-shadow: 0 1px 3px rgba(0,0,0,.25), 0 0 0 1px #2563d8,inset 0 2px 6px rgba(0,0,0,.2); }.btn-candy:focus-visible { outline: 3px solid rgba(41,111,240,.45);outline-offset: 2px; }
Props
| prop | type | default | description |
|---|---|---|---|
| color | string | "#296FF0" | Base color — gloss, ring, glow & focus all derive from it. |
| radius | number | 18 | Corner radius in px. |
| icon | ReactNode | — | Optional leading icon (e.g. lucide <Plus />). |
| labelColor | string | "#fff" | Label / icon color. |
| children | ReactNode | — | Button label. |
| disabled | boolean | false | Dims and disables; cancels hover/press motion. |
| …props | ButtonHTMLAttributes | — | Any native button prop (onClick, type, aria-*). |