Built on Base UI's Button.
import { Button } from "@/components/eased/button/button";
<Button variant="primary" loading={pending} onClick={save}>{saved ? "Saved" : "Save"}</Button>
<Button variant="ghost" size="icon-sm" aria-label="Copy link"><LinkIcon /></Button>
<Popover.Trigger render={<Button />}>Share</Popover.Trigger> {/* never scales */}| Prop | Type | Default | |
|---|---|---|---|
variant | "primary" | "secondary" | "ghost" | "danger" | "danger-solid" | "secondary" | One primary per region. danger is outlined; danger-solid is for the final confirm step only. |
size | "sm" | "md" | "lg" | "icon-sm" | "icon" | "icon-lg" | "md" | 32, 36 and 40px; icon sizes are square. |
loading | boolean | false | Eased's Spinner over a transparent label: width kept, focus kept, clicks ignored, aria-busy. |
swapKey | string | number | Swap the label when children are elements. String labels swap on their own. | |
scaleOnPress | boolean | true | Off automatically for aria-haspopup triggers. |
hitArea | boolean | true for icon-sm | 44×44 tap target around a small button. Pass false when the button sits beside other targets (toolbars, rows), so the extended area does not cover them. |
Plus every Base UI Button prop (disabled, focusableWhenDisabled, render …). type defaults to "button". Announce a "Saved" state with your own role="status" region: content inside a button is not announced.
Motion signature: the press scale follows the width (0.96 icon, 0.97, 0.985 past 220px); a label change swaps with y + blur while the width springs, and loading keeps the width, so the button never jumps.