---
title: Button
description: "Save, send, or follow a link from a control that does not stay pressed. For %framework% with %library%, when the next step is a single action on the page."
category: Buttons
---

One press for a click, a form submit, or a link that should look like a button.

Use it for Save or Send. When the control should stay on, use a [Toggle](/ui/toggle). In React, Base UI renders a real button, and the default doesn't submit the form.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

## Installation

<CodeTabs>

<TabsList>
<TabsTrigger value="cli">CLI</TabsTrigger>
<TabsTrigger value="manual">Manual</TabsTrigger>
</TabsList>

<TabsContent value="cli">

```bash
npx shadcn@latest add https://www.shadcn.io/r/button.json
```

</TabsContent>

<TabsContent value="manual">

<Steps>

<Step>This component depends on [Spinner](/ui/spinner). Install it first if you haven't already.</Step>

<Step>Install the following dependencies:</Step>

```bash
npm install @base-ui/react tailwind-variants
```

<Step>Add the following to your `globals.css`.</Step>

<ComponentCss name="button" base="base" />

<Step>Copy and paste the following code into your project.</Step>

> The source is free with an account: https://www.shadcn.io/ui/button. Or install it with the command above.

<Step>Update the import paths to match your project setup.</Step>

</Steps>

</TabsContent>

</CodeTabs>

## Anatomy

<ComponentAnatomy name='button' />

## Usage

```tsx
import { ArrowUpIcon } from "lucide-react";
import { Button } from "@/components/ui/button";
```

```tsx
<div className="flex flex-wrap items-center gap-2 md:flex-row">
  <Button variant="outline">Button</Button>
  <Button aria-label="Submit" size="icon-md" variant="outline">
    <ArrowUpIcon aria-hidden="true" />
  </Button>
</div>
```

## Accessibility

<KeyboardTable>
<KeyboardTableItem keys="Enter">
Activate the focused control.
</KeyboardTableItem>
<KeyboardTableItem keys="Space">
Activate the focused control.
</KeyboardTableItem>
</KeyboardTable>

## Demos

### Size

`size` runs from `xs` to `lg` in this row, each label paired with a square `icon-*` sibling. Extra small belongs in a dense toolbar, small sits beside body text, and the default is the action the screen is about. Icon sizes stay square so a lone glyph does not stretch, and `xl` or `icon-xl` is there when the button has to be read from across a room.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Variant Default

`variant="default"` is the filled button, the one that should win a glance. Spend it on the action you want taken, such as save or pay, and let everything else drop to outline or ghost. A row of filled buttons is just several buttons arguing.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Outline

`variant="outline"` keeps a border and drops the fill, which is the right weight beside a primary action. Cancel next to Save, or a filter that should look pressable without shouting, both sit well here. It still reads as a button before anyone hovers, which is where ghost starts to vanish.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Secondary

`variant="secondary"` fills the button in a quieter color, for an action that is real but not the preferred one. Pair it with the default variant when both deserve a fill, such as Save and Save as draft. A border with no fill is outline instead, and it is the lighter of the two.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Ghost

`variant="ghost"` shows no border until hover or focus, so it fits a low-stakes action in a toolbar or a table row. Do not use it for the only action on a page. People skip past it. Reach for outline instead when the control has to stay visible at rest.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Destructive

`variant="destructive"` is the red control for delete, remove, and anything that should not be casual. Put that color on the button that confirms, not on the one that only opens a dialog. One red control in a group is plenty, and the others should stay neutral so the danger stays obvious.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Link

`variant="link"` only paints the button as underlined text, and it does not navigate. It fits a tertiary action inside a sentence, such as forgot-password beside a field label. When the press should go somewhere, put the same styles on a real link, which is the As Child demo rather than this one.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Icon

An icon-only button uses an `icon-*` size so the target stays square, and this one is `icon-md`. The glyph is `aria-hidden`, so `aria-label` has to carry the name, here Submit. Use the pattern for a repeated action whose meaning is already obvious, and keep a visible word when the icon could mean two things.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### With Icon

The icon lives in the button, and `data-icon` is `inline-start` or `inline-end` so the gap is right and a right-to-left layout can mirror it. Start is the side for a new-branch mark. End is the side for an arrow that points onward. Drop the attribute and the icon sits on the words.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Rounded

The buttons add `rounded-full`, and the `pill` prop does the same thing without a one-off class. A capsule suits a single marketing action, or a round icon that floats over content. In an app the default radius is calmer, since a row of pills starts to look like a set of tags. Match the icon button, or it stays a rounded square beside a capsule.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Spinner

A [Spinner](/ui/spinner) beside the label keeps the action readable while it runs, as with Generating and Downloading. The button is `disabled`, and `data-icon` on the spinner holds the gap an icon would. Keep the words when the name still matters. `isLoading` is the other pattern: it replaces the label with a centered spinner, holds the width, and sets `aria-busy`.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### As Child

Login here is a real link wearing button styles, so it navigates and still matches the other controls. Use that when the destination is an address, a login page or a pricing page. `variant="link"` only changes the paint. The prop that merges styles differs by library, and the link question in the FAQ names each one.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

### Rtl

`dir="rtl"` on the row flips the cluster: Arabic labels, a destructive action, an icon button, and a loading label, with the arrow on `data-icon="inline-end"` so it mirrors. Use the attribute when this group is an island of right-to-left UI. A larger subtree should take [Direction](/ui/direction) instead of a `dir` on every wrapper. A physical margin on the icon will fight that flip.

> The code for this demo is free with an account: https://www.shadcn.io/ui/button

## FAQ

<FaqList>
<FaqItem id="link" question="How do I make a Button that links to another page in React?">

Pass your link with `render`, such as `render={<a href="/pricing" />}`, as in the [As Child](#as-child) demo.

`variant="link"` only changes the look; it doesn't navigate.
</FaqItem>
<FaqItem id="submit" question="Why doesn't my Button submit the form?">
Its `type` is `button` by default, so it never submits a form by accident. Pass `type="submit"` to the one button that should submit.
</FaqItem>
<FaqItem id="loading" question="How do I show a loading spinner on a Button?">
Pass `isLoading`: the button shows a [Spinner](/ui/spinner) in place of its label, keeps its width, is disabled and sets `aria-busy`. The label stays available to screen readers, and the [Spinner](#spinner) demo shows a spinner next to the text instead.
</FaqItem>
<FaqItem id="variants" question="What Button variants and sizes are there?">
`variant` takes `default`, `secondary`, `outline`, `ghost`, `destructive` or `link`. `size` goes from `xs` to `xl`, and `icon-xs` to `icon-xl` makes a square icon button; shadcn/ui's `default` and `icon` work too, as other names for `md` and `icon-md`. `pill` rounds it fully; the [Size](#size) and variant demos show them all.
</FaqItem>
<FaqItem id="icon" question="How do I add an icon to a Button?">
Put the icon inside the button with `data-icon="inline-start"` or `data-icon="inline-end"`, as in the [With Icon](#with-icon) demo, so the spacing adjusts and right-to-left layouts mirror it. An icon-only button needs an `aria-label`, as in the [Icon](#icon) demo.
</FaqItem>
<FaqItem id="disabled" question="Should I disable a Button or hide it when an action isn't available?">
Usually disable it: a missing button looks like a bug, while a disabled one shows the action exists. Explain why nearby, since a disabled button can't show a tooltip on hover in most browsers.

</FaqItem>
<FaqItem id="click-handler" question="How do I handle a Button click in React?">
Pass `onClick`, as with any button.

</FaqItem>
<FaqItem id="install" question="How do I install the Button in React?">
Run the command in the Installation section above: it adds `button` to your `components/ui` folder with the [Spinner](/ui/spinner) it uses. React and Solid use the shadcn CLI, Vue uses shadcn-vue and Svelte uses shadcn-svelte, each after its one-time setup.
</FaqItem>
</FaqList>

## API Reference

### Button

`<button>` `data-slot="button"`

<TypeTable>
<TypeTableItem name='clickEffect' type='boolean' optional defaultValue='true'>
Apply a click effect to the button.
</TypeTableItem>
<TypeTableItem name='focusableWhenDisabled' type='boolean' optional defaultValue='false'>
Whether the button should be focusable when disabled.
</TypeTableItem>
<TypeTableItem name='isLoading' type='boolean' optional defaultValue='false'>
Show a loading indicator.
</TypeTableItem>
<TypeTableItem name='nativeButton' type='boolean' optional defaultValue='true'>
Whether the component renders a native `<button>` element when replacing it
via the `render` prop.
Set to `false` if the rendered element is not a button (for example, `<div>`).
</TypeTableItem>
<TypeTableItem name='pill' type='boolean' optional defaultValue='false'>
Fully rounded corners.
</TypeTableItem>
<TypeTableItem name='size' type='"default" | "icon" | "icon-lg" | "icon-md" | "icon-sm" | "icon-xl" | "icon-xs" | "lg" | "md" | "sm" | "xl" | "xs"' optional defaultValue='"md"'>
The size.
</TypeTableItem>
<TypeTableItem name='variant' type='"default" | "destructive" | "ghost" | "link" | "outline" | "secondary"' optional defaultValue='"default"'>
The visual style.
</TypeTableItem>
<TypeTableItem name='render' type='ReactElement | ((props: object, state: object) => ReactElement)' optional>
Replace the host element or compose it with another component.
</TypeTableItem>
<TypeTableItem name='className' type='string' optional>
Merged with the part's classes.
</TypeTableItem>
</TypeTable>

<TypeTable nameColumn='Attribute'>
<TypeTableItem name='data-pill' type='string' optional>
Present when `pill` is set.
</TypeTableItem>
<TypeTableItem name='data-size' type='string' optional>
Mirrors `size`.
</TypeTableItem>
<TypeTableItem name='data-state' type='string' optional>
"loading" when `isLoading` is true, otherwise "idle".
</TypeTableItem>
<TypeTableItem name='data-variant' type='string' optional>
Mirrors `variant`.
</TypeTableItem>
</TypeTable>
