Button
A button starts an action, such as saving a form, sending a message or opening a dialog.A button starts an action when someone presses it: it saves, sends, deletes or opens something. A button that stays on or off is a Toggle. A link that takes someone to another page is a link, even when it looks like a button.
Playground
Usage
import { Button, Flex } from '@kushagradhawan/kookie-ui';
export function SaveBar({ saving, onCancel, onSave }: { saving: boolean; onCancel: () => void; onSave: () => void }) {
return (
<Flex gap="2" justify="end">
<Button emphasis="quiet" color="gray" onClick={onCancel}>
Cancel
</Button>
<Button emphasis="loud" loading={saving} onClick={onSave}>
Save changes
</Button>
</Flex>
);
}Best practices
Give each screen one loud button. Set emphasis="loud" on the action people are most likely to take, and let the others stay medium or quiet. Two loud buttons side by side give people no answer to "which one do I press?"
Rank buttons with emphasis, not with size. Buttons in one row share one size, and emphasis tells them apart. A larger button reads as a different kind of control, not as the more important choice.
Start the label with a verb. "Save changes", "Send invite" and "Delete project" tell people what happens. "OK", "Yes" and "Submit" make them read the rest of the screen to find out.
Don't make a destructive action the loud one. Use color="red" for a button that deletes something, and confirm it in an Alert Dialog when it cannot be undone. A loud red button invites the press it should slow down.
Prefer a message to a disabled button. A disabled button doesn't say why it is disabled. Where you can, keep it enabled and explain what is missing when someone presses it.
Emphasis
emphasis sets how much attention a button asks for. Each value maps to one look, so you choose the rank and the theme chooses the style.
| Emphasis | Look | Use it for |
|---|---|---|
loud | solid | The main action on the screen. This is the default |
medium | raised | Most other buttons |
quiet | ghost | Cancel, close and actions in a toolbar |
variant sets the look directly, when you need soft, surface or outline, which have no rank of their own. Pass emphasis or variant, not both. The types reject the pair. If both arrive anyway, the button logs an error and emphasis wins.
Size
size picks one of four heights: 28, 32, 40 and 48 pixels. Size 2 is the default and suits most screens. Use size 1 in dense toolbars and size 3 or 4 where a touch target needs room. A button is as tall as a TextField or a Select of the same size, so a row of controls lines up. size accepts a responsive object, such as { initial: '3', md: '2' }.
Examples
Icon and label
Put the icon before the label, inside the button. The button sizes the icon and sets the gap, so don't size it yourself.
<Button>
<HugeiconsIcon icon={Add01Icon} strokeWidth={1.75} />
New project
</Button>Icon only
iconOnly makes the button square. It has no visible text, so the types require aria-label or aria-labelledby. Add tooltip so people who use a pointer can see the name too.
<Button iconOnly emphasis="quiet" aria-label="Search" tooltip="Search">
<HugeiconsIcon icon={Search01Icon} strokeWidth={1.75} />
</Button>Waiting for a result
loading replaces the content with a spinner and blocks presses until it clears, so a slow request can't be sent twice. The button keeps its width and its focus, so the layout doesn't jump and keyboard users stay in place. Pass a boolean every time, such as loading={saving}, so the content stays mounted when loading starts and stops.
Confirming a copy
done shows a tick in place of the leading icon. A button with no icon adds the tick before the label, so it grows by the tick's width. The icon comes back when done is false. It doesn't block presses. Screen readers don't announce the tick, so change the label too.
const [copied, setCopied] = React.useState(false);
function copy() {
navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 1500);
}
<Button iconOnly emphasis="quiet" done={copied} aria-label={copied ? 'Copied' : 'Copy link'} onClick={copy}>
<HugeiconsIcon icon={Copy01Icon} strokeWidth={1.75} />
</Button>Navigation
When a button-shaped control goes to another page, render your router's link with asChild. The element stays an anchor, so it opens in a new tab and shows its address like any link.
<Button asChild>
<Link href="/settings">Open settings</Link>
</Button>Full width
fullWidth stretches the button across its container. Use it for the single action at the end of a narrow form or a mobile sheet. It isn't available with iconOnly.
Props
| Prop | Type | Default | Description |
|---|---|---|---|
emphasis | 'loud' | 'medium' | 'quiet' | 'loud' | How much attention the button asks for |
variant | 'raised' | 'solid' | 'soft' | 'surface' | 'outline' | 'ghost' | 'solid' | The look, set directly. Not with emphasis |
size | '1' | '2' | '3' | '4', responsive | '2' | The height step |
color | AccentColor | Theme accent | The colour. Gray fills with step 12 |
radius | 'none' | 'small' | 'medium' | 'large' | 'full' | Theme radius | The corner |
loading | boolean | false | Shows a spinner and blocks presses |
done | boolean | false | Shows a tick in place of the icon |
disabled | boolean | false | Blocks presses and shows the disabled colours |
iconOnly | boolean | false | Square, icon only. Requires aria-label or aria-labelledby |
fullWidth | boolean | false | Fills the container's width. Not with iconOnly |
tooltip | ReactNode | Shows on hover and on keyboard focus | |
tooltipSide | 'top' | 'right' | 'bottom' | 'left' | 'top' | Where the tooltip opens |
tooltipAlign | 'start' | 'center' | 'end' | 'center' | How the tooltip lines up with the button |
asChild | boolean | false | Renders the child element, such as a link, with the button's look |
Button also accepts every attribute of the HTML button element.
Accessibility
| Key | Action |
|---|---|
| Enter or Space | Presses the button |
| Tab | Moves focus to the next control |
- Name: A screen reader announces the label. An icon-only button announces its
aria-label, which the types require. - Loading: The button sets
aria-busyand announces that it is loading. It stays in the tab order, so focus doesn't jump to the top of the page. - Done: The tick is hidden from screen readers. Change the label or
aria-label, such as "Copy" to "Copied", so the change is heard. - Disabled: A disabled
buttonelement leaves the tab order. A disabled link orasChildelement setsaria-disabledand ignores presses, including the child's ownonClick. - Tooltip: The tooltip adds to
aria-describedby. A description you pass yourself is kept. - Focus: The focus ring shows for the keyboard only, and it uses the button's colour.