Button
A Button allows users to start an action, such as submitting a form or sending an invoice.
When to Use
- to start an action on the current page, such as “Save changes” or “Send invoice”
- to submit a form
- as the trigger of a Dialog, a Sheet, or a Dropdown Menu
- as an icon-only Button, for an action that repeats on every row of a Table, where a label doesn’t fit
When Not to Use
- to navigate to another page (use a link)
- for an option that stays on after a click (use a Toggle)
- to choose one of a few options (use a Segmented Control)
- to offer several actions from one trigger (use a Dropdown Menu)
Anatomy
| Element | Usage |
|---|---|
| Label* | Optional |
| Container | Required |
| Leading icon* | Optional |
| Focus outline | Keyboard focus only |
| Trailing icon* | Optional |
* A Button has a label, an icon, or both.
Variants
| Variant | Purpose |
|---|---|
default | The main action of a page, a form, or a Dialog |
outline | The trigger of a popup and an action beside the main one |
secondary | A Button joined to another control in a Button Group or an Input Group |
ghost | An action in a toolbar, a Card header, or a Table row |
destructive | An action that deletes, such as “Delete” in an Alert Dialog |
link | An action that appears with text, such as “Show the full message” under a message |
Use default for the most important action in a group, such as submitting a form. Avoid using more than one default Button in a group, as users can no longer tell which action completes the task. Use outline or ghost for the other actions.
Sizes
| Size | Height | Placement |
|---|---|---|
xs | 24px | Inside a Table row or another control |
sm | 28px | A dense toolbar with no Input |
default | 32px | Everywhere else |
lg | 36px | The one main action of a sparse surface |
An icon-only Button is square and has the same heights at icon-xs, icon-sm, icon, and icon-lg.
Use the same size for every Button in a row so that their top and bottom edges line up. See Sizing for the rule on controls in a row.
States
To show that a request is in progress, use a Loading Button.
If you want a disabled Button to stay in the tab order, set focusableWhenDisabled. The Button then has aria-disabled in place of disabled.
Behavior
By default, a Button is as wide as its content. The label doesn’t wrap, and the Button doesn’t shrink in a flex row, so a long label will result in a wide Button.
A Button renders type="button" by default. If the Button submits a form, set type="submit".
In a right-to-left layout, icons aren’t mirrored. If the icon is an arrow, add rtl:rotate-180.
Content
| String | Rule | Example | Counterexample |
|---|---|---|---|
| Label | Sentence case | “Send invoice” | “Send Invoice” |
| Label | Start with the verb that names the action | “Create invoice” | “New invoice” |
aria-label | Name the action, and name the record when the Button repeats in a list | “Actions for INV-2026-014” | “Actions” |
Accessibility
A Button renders a native button element, so the browser provides its role, its place in the tab order, and its two keys, Enter and Space.
Requirements
An icon-only Button has no text, so it needs an aria-label for screen readers to announce it, as
WCAG 2.2 SC 4.1.2 Name, Role, Value requires. Show the same words in a Tooltip, not in a title
attribute. The HTML Standard discourages
relying on title because many browsers need a pointing device to show it, which excludes users who use only a
keyboard or a touch screen.
Installation
npx shadcn@latest add @summit/buttonUsage
import { Button } from '@/components/ui/button';
<Button>Save changes</Button>;With an Icon
To place an icon before or after the label, mark it with data-icon="inline-start" or data-icon="inline-end".
import { EnvelopeIcon } from '@phosphor-icons/react';
<Button>
<EnvelopeIcon data-icon="inline-start" />
Send invoice
</Button>;Icon Only
For an icon-only Button, set one of the icon sizes and an aria-label, and pass the Button to TooltipTrigger through render.
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '@/components/ui/tooltip';
import { PlusIcon } from '@phosphor-icons/react';
<TooltipProvider>
<Tooltip>
<TooltipTrigger render={<Button variant="outline" size="icon" aria-label="Add client" />}>
<PlusIcon />
</TooltipTrigger>
<TooltipContent>Add client</TooltipContent>
</Tooltip>
</TooltipProvider>;As a Trigger
If you want a Button to open a Dialog or another popup, pass it to the trigger’s render prop. The trigger provides the label.
import { DialogTrigger } from '@/components/ui/dialog';
<DialogTrigger render={<Button variant="outline" />}>Rename project</DialogTrigger>;Links as Buttons
If a link needs to look like a Button, keep it an a and style it with buttonVariants, which returns the class string for a variant and a size. Button enforces button semantics, so it must not render a link. See the Base UI Button documentation for the reason.
import { buttonVariants } from '@/components/ui/button';
<a href="/invoices" className={buttonVariants({ variant: 'outline' })}>
View all invoices
</a>;API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'default' | 'outline' | 'secondary' | 'ghost' | 'destructive' | 'link' | 'default' | The fill and the text color |
size | 'default' | 'xs' | 'sm' | 'lg' | 'icon' | 'icon-xs' | 'icon-sm' | 'icon-lg' | 'default' | The height, padding, and text size |
Other props are passed to Base UI’s Button. See the Base UI Button documentation for render, nativeButton, and focusableWhenDisabled.