Button
Runs an action on the page, like Save, Edit or New.
Installation
The CLI installs the Radix or Base UI file to match your project.
If your project doesn't have Wisty UI's theme yet, add it once with npx shadcn@latest add @wisty/cosmos. Until then, components keep shadcn's default look. Installation has the details.
When to use
Use it
- To run an action on the current page: save a record, submit a form, open a dialog or start a flow.
- For the main action of a page, form or dialog. Use
default(SLDS brand) and keep it to one per view. - For the actions next to it, such as Cancel or Edit. Use
outline(SLDS neutral). - For low-emphasis actions in toolbars, card headers and table rows. Use
ghost, or an icon button with anaria-label. - To confirm a delete or another action you can't undo. Use
destructive, ordestructive-textwhen the delete isn't the main action. - For approve, complete and other positive actions. Use
success. - On a dark surface such as
bg-inverse. Useinverse.
Don't use it
- To go to another page or URL. Use a link (
<a>or your router'sLink). If it has to look like a button, give the linkclassName={buttonVariants()}, as in As a link. - For a link inside a sentence. Use a plain
<a>. Thelinkvariant is for standalone actions. - To turn a setting on or off. Use Switch, or Toggle when the control shows a pressed state.
- To pick one option from a small set. Use Tabs to switch views, or Toggle Group or Radio Group to pick a value.
- To show a status, label or count. Use Badge.
- To offer several related actions in a small space. Use Dropdown Menu with a Button as its trigger.
Usage
Pick a variant and a size. An icon goes before or after the label as a child; an icon-only button takes size="icon" and an aria-label.
import { Button } from "@/components/ui/button"<Button>Save</Button>A link that should look like a button takes buttonVariants() as its class, as the As a link example shows.
Examples
Variants
Every `variant`, with the four added ones on the lower rows and `inverse` on `bg-inverse`.
Sizes
Each text `size` on the first row and each icon `size` on the second.
With icon
Icons as children, after or before the label, and `size="icon"` buttons in brand and `ghost`.
Disabled
Bordered, filled and base buttons with `disabled`.
As a link
Links to another page, styled as neutral and brand buttons with `buttonVariants()` as their class.
API reference
Button
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "default" | "outline" | "secondary" | "ghost" | "destructive" | "link" | "outline-brand" | "destructive-text" | "success" | "inverse" | "default" | |
size | "default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg" | "default" | |
asChild | boolean | false | Renders the child element in its place, with the props merged onto it. |
Also takes every <button> (opens in a new tab) prop.
SLDS mapping
Button draws SLDS 2's button on shadcn's props: brand, neutral, the base button, destructive and the text link, plus four variants SLDS has and shadcn doesn't. The table lists each value's SLDS class. secondary is neutral too, since SLDS has no filled gray button. Icon buttons color their glyph as SLDS icon buttons do: the link blue in Cosmos, gray in Lightning Blue.
Known gaps:
- Labels don't wrap. SLDS lets a long label wrap onto a second line.
- No spacing between buttons. SLDS puts 4px between siblings; here you set a gap on the parent.
- A
ghosticon button is a 32px box, like SLDS's container icon button. SLDS's bare icon button is only as big as its glyph. - Not covered: stateful buttons, selected icon buttons, and the bare, error, warning and more icon button styles. Rows of joined buttons are Button Group.
| Prop | Value | SLDS | Notes |
|---|---|---|---|
variant | default | slds-button_brand | |
variant | outline | slds-button_neutral | |
variant | secondary | slds-button_neutral | SLDS has no filled gray button. |
variant | ghost | slds-button | The base button: no border, link color. |
variant | destructive | slds-button_destructive | |
variant | link | slds-button | The base button, underlined on hover, with no padding. |
variant | outline-brand (added) | slds-button_outline-brand | |
variant | destructive-text (added) | slds-button_text-destructive | |
variant | success (added) | slds-button_success | |
variant | inverse (added) | slds-button_inverse | |
size | default | slds-button | 32px. |
size | xs, sm, lg | none | SLDS has one text button size; these keep shadcn's sizes. |
size | icon | slds-button_icon-border-filled | 32px; round in Cosmos. |
size | icon-xs | slds-button_icon-small | 24px. |
size | icon-sm, icon-lg | none | 28px and 40px, kept from shadcn. |
Accessibility
- Icon-only buttons need an
aria-label. Lucide icons renderaria-hidden, so the label is the button's only name. - Inside a form, a button without a
typesubmits it. Settype="button"on every button that isn't the submit action. - A disabled button can't take focus or show a tooltip. Say why an action is unavailable in nearby text.
- A link styled as a button follows Enter but not Space, as any link does, and
disableddoes nothing on it. - The focus ring shows on keyboard focus only. It's a box shadow, so in forced-colors mode it becomes a 2px outline.
inverseis made forbg-inverse. Check contrast before you put it on another surface.
Motion
- In Cosmos, hovering lifts a button: 1.5px for bordered variants, 2px for filled ones, each with an edge shadow. Pressing drops it. Lightning Blue sets the lift and the shadow to none, so its buttons only change color.
- A button whose popup is open (
aria-expanded) doesn't lift, so a popover anchored to it stays still. - The lift runs with reduced motion too. It's 2px over 150ms, and SLDS has no reduced-motion rule for buttons either.
Responsive behavior
- Labels don't wrap, so a long label overflows a narrow container. Keep labels short and let rows of buttons wrap with
flex-wrap. - For a full-width button on small screens, add
w-full. That's SLDS's stretch button, with the label still centered. xsandicon-xsare 24px, WCAG's minimum target size. For touch-first screens uselgandicon-lg, at 40px.
Rules
- One
defaultbutton per view or dialog, for the main action. The rest areoutline, orghostin dense toolbars. - Never override radius, colors, font weight, height or shadows with utility classes such as
rounded-md,bg-blue-600,font-medium,h-9orshadow-sm. The tokens carry each theme, and an override pins one look into every theme. Layout classes such asw-full, margins andself-endare fine. - Put Lucide icons in as bare children. The button sizes and spaces them; give one a
size-*class only to change its size. - Space side-by-side buttons with
gap-2on the parent. Button has no margin of its own. - Prefer
size="default",iconandicon-xs.xs,sm,lg,icon-smandicon-lghave no SLDS counterpart. - Use
inverseonly on dark surfaces such asbg-inverse. - For navigation, style the link itself:
<a href="/setup" className={buttonVariants()}>Setup</a>. Never put a button inside a link, or a link inside a button.