Summit
ComponentsActions

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

ElementUsage
Label*Optional
ContainerRequired
Leading icon*Optional
Focus outlineKeyboard focus only
Trailing icon*Optional

* A Button has a label, an icon, or both.

Variants

VariantPurpose
defaultThe main action of a page, a form, or a Dialog
outlineThe trigger of a popup and an action beside the main one
secondaryA Button joined to another control in a Button Group or an Input Group
ghostAn action in a toolbar, a Card header, or a Table row
destructiveAn action that deletes, such as “Delete” in an Alert Dialog
linkAn 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.

DoUse the filled Button for the one action that completes the task.
Don’tWhen both Buttons are filled, “Cancel” looks as important as “Save”, so users can’t tell which action keeps the change.

Sizes

SizeHeightPlacement
xs24pxInside a Table row or another control
sm28pxA dense toolbar with no Input
default32pxEverywhere else
lg36pxThe 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.

DoUse the same size for all three Buttons; each is 32px tall.
Don’tWhen sizes are mixed in a row, the edges no longer line up, and “Filter” is a 24px target next to a 36px one.

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

StringRuleExampleCounterexample
LabelSentence case“Send invoice”“Send Invoice”
LabelStart with the verb that names the action“Create invoice”“New invoice”
aria-labelName 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/button

Usage

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".

With an Icon
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.

Icon Only
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.

As a Trigger
import { DialogTrigger } from '@/components/ui/dialog';

<DialogTrigger render={<Button variant="outline" />}>Rename project</DialogTrigger>;

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.

Links as Buttons
import { buttonVariants } from '@/components/ui/button';

<a href="/invoices" className={buttonVariants({ variant: 'outline' })}>
    View all invoices
</a>;

API Reference

PropTypeDefaultDescription
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.

On this page