Button
A button for actions: four variants, three sizes, a loading state, icons and a navigation arrow.
import { Button } from "@butteryui/react";Demo
When to use it
Use it for
- Actions: save, export, add, open a dialog
- Steps forward and back with arrow="forward" or arrow="back": Next, Back, Continue, Get started
- A link that should look like a button, with asChild
- A main action with its keyboard shortcut shown after the label, with shortcut="n"
Not for
- An action shown only as an icon: use IconButton
- A link inside text or a quiet 'See all': use ArrowLink
- Picking one of a few options: use SegmentedControl
- A main action with a few alternatives: use SplitButton
Examples
Add to order
One action and what it changes: each press adds a bag to the order.
0 bags in your order
Link button
A link dressed as a call to action, with the forward arrow.
Save and cancel
A form's closing pair: the main action in primary, the way out in soft.
You have unsaved changes
Saving
A save that takes a moment: the button shows a spinner and holds its width until it's done.
Not saved yet
Step buttons
Back and Next with arrows in equal columns; the last step finishes, so its button drops the arrow.
Variants and sizes
The four variants, from the page's main action to its quietest, and the three sizes.
Last action: Nothing yet
Variants and states
Variants
Primary for the one main action in view, soft for the rest, secondary on tinted surfaces, ghost for quiet actions.
Sizes and shapes
28, 36 and 48px tall; rounded corners for dialog steps.
Arrows
Forward and back steps. Hover or focus one: the chevron grows a stem and the button keeps its width.
Icons
With a shortcut
The key that does the same thing, after the label in the button's own color. Bind it with useShortcut.
Loading
A spinner over the label; the button keeps its size and ignores presses.
Disabled
Pass aria-disabled for an action that isn't available yet, such as Save before a form is complete or Back on a first step: the button looks like this and ignores presses but stays in the Tab order, so focus doesn't drop to the page when it turns off and screen readers still find it. Pass disabled only for an action that stays off for as long as the screen is open, such as one the person has no permission for.
As a link
Props
Button
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "soft" | "ghost" | "soft" | "primary" for the one main action in view, "soft" (the default) for the rest, "secondary" on a
tinted surface such as the stage, and "ghost" for quiet actions in toolbars. |
size | "sm" | "md" | "cta" | "md" | "sm" (28px), "md" (36px, the default) or "cta" (48px, for dialog steps and calls to action). |
shape | "pill" | "rounded" | "pill" | "pill" (the default) or "rounded", with 12px corners, for calls to action in dialogs. |
loading | boolean | Shows a spinner over the label, keeping the button's size, and ignores presses while it's busy. | |
iconStart | ComponentType<IconComponentProps> | An icon before the label, such as Plus from lucide-react. | |
iconEnd | ComponentType<IconComponentProps> | An icon after the label. | |
arrow | "forward" | "back" | A navigation arrow: "forward" after the label for Next, Continue and Get started, "back" before it.
Its chevron grows a stem on hover or focus. Leave it off Done, Save, Submit and destructive actions. | |
shortcut | Shortcut | A keyboard shortcut to show after the label, written as useShortcut takes it, such as "n" or
"mod+n". It's announced through aria-keyshortcuts; bind it with useShortcut. | |
asChild | boolean | Render the single child element (such as a link) as the button, with the button's look and parts merged in. The child's own content is the label. |
It also takes ComponentPropsWithRef<"button">.
Accessibility
A native button, or the element you give it with asChild, named by its label; type="button" by default. loading sets aria-busy and aria-disabled and ignores presses, keeping focus where it is. aria-disabled on its own does the same without the spinner: the button stays in the Tab order and screen readers announce it as unavailable, while disabled takes it out of the Tab order, so keyboard and screen-reader users pass it without hearing it. A shortcut is announced through aria-keyshortcuts; bind it with useShortcut. Icons, the arrow and the spinner are hidden from assistive technology.
| Keys | What they do |
|---|---|
| Enter or Space | Presses it, unless it's loading or aria-disabled |
| Enter | Follows the link, when it's a link with asChild |
Motion
It scales to 97% while it's pressed, over `--bt-dur-press`, and springs back when it's let go.
With reduced motion: It presses in at once, and an arrow grows its stem at once, without the nudge.
Layout
Its height is its size's token: --bt-h-sm (28px), --bt-h-button (36px) or --bt-h-cta (48px). It's as wide as its label and padding, never wider than its container, where a long label truncates with an ellipsis and shows in full as its title. The whole pill is the hit area, and loading keeps its size.
Related
See it in the roastery demo's overview and settings. See it in the Halfmoon shop demo. See it in the Deckhand demo.