Skip to content
shadcn.io
shadcn.io is not affiliated with official shadcn/ui

Shadcn Button for React and Tailwind

Triggers an action or navigates when used as a link.

Installation

bunx --bun shadcn@latest add https://kit.dev/r/button.json

Anatomy

A single control. Icons are children. isLoading replaces the visible label with a spinner and keeps the text for assistive tech.

Button
└── content (text, icon, …)

Usage

import { Button } from "@/components/ui/button";
<Button>Save</Button>

Button defaults to type="button". In a form, set type="submit" (or reset) explicitly.

asChild merges button styles onto a single child. Use it for Link or a. Do not combine asChild with isLoading.

States

Disabled

Loading

isLoading sets data-state="loading", aria-busy, and disabled, and shows a Spinner without changing the button’s width.

Variants

Default

Outline

Secondary

Ghost

Text-styled. For real navigation, prefer asChild with Link over variant="link" on a <button>.

Destructive

Sizes

SizeHeightNotes
xsh-6text-xs, rounded-sm
smh-7
mdh-8Default
lgh-9
xlh-10text-base
icon-xssize-6Square, rounded-sm
icon-smsize-7Square
icon-mdsize-8Square
icon-lgsize-9Square
icon-xlsize-10Square, icon size-5

There is no size="icon" or size="default". Use icon-md and md.

Extra small

Small

Medium

Large

Extra large

Icon sizes

Icon-only buttons need aria-label.

Pill

pill uses rounded-full. With an SVG child, end padding increases so the icon is not clipped.

Click effect

The default press scale is off when the button has aria-haspopup (menus, selects). Set clickEffect={false} to disable it everywhere.

Examples

Icon only

With icon

Put the icon before the label (or after, for an external-link cue). Decorative icons should be aria-hidden="true".

Touch hitbox

Expand the tap target with Hitbox without changing layout.

Custom color

Override background, text, and shadow with className. Prefer semantic tokens when a variant already exists.

Guides

asChild

asChild comes from Ark’s factory. The child must be a single element:

<Button asChild variant="outline">
  <Link href="/login">Login</Link>
</Button>

type="button" is omitted when asChild is set so it is not copied onto an <a>.

Forms

The default type is "button" so a Button inside a <form> does not submit. Use type="submit" for the submitting action.

ButtonButton Groupvariant="link"
RoleOne actionVisually connected actionsLooks like a text link
NavigationasChild + Link—Still a button unless asChild

Using buttonVariants

import { buttonVariants } from "@/components/ui/button";

<a className={buttonVariants({ size: "sm", variant: "outline" })} href="/docs">
  Docs
</a>

API Reference

shadcn.io Button is a styled button (Ark factory), not a Zag machine. There is no context hook or root provider.

asChild merges props onto a single child element.

Button

Root. Renders a button.

PropTypeDefaultDescription
variant"default" | "outline" | "secondary" | "ghost" | "link" | "destructive""default"Color treatment.
size"xs" | "sm" | "md" | "lg" | "xl" | "icon-xs" | "icon-sm" | "icon-md" | "icon-lg" | "icon-xl""md"Height, padding, and icon box. See Sizes.
pillbooleanfalseFully rounded ends. Extra end padding when an SVG child is present.
clickEffectbooleantrueScale down on press, except when aria-haspopup is set.
isLoadingbooleanfalseDisable the control, set aria-busy, and show a spinner. Ignored with asChild.
type"button" | "submit" | "reset""button"Native type. Omitted when asChild.
disabledbooleanfalseNative disabled. Also set when isLoading.
asChildbooleanfalseRender the child element instead of a button.
classNamestring-Class names on the root.

Native button attributes (name, value, form, aria-label, aria-haspopup, …) pass through.

AttributeDescription
data-slotbutton
data-variant"default", "outline", "secondary", "ghost", "link", or "destructive"
data-sizeThe size token
data-state"loading" or "idle"
data-pill"true" when pill is set
aria-busyPresent when isLoading
disabledPresent when disabled or isLoading

buttonVariants

Exported tv() recipe. Same variant, size, pill, and clickEffect keys as Button.

buttonVariants({ size: "sm", variant: "outline" });

ButtonProps

Props of Button: button attributes plus VariantProps<typeof buttonVariants>, clickEffect, and isLoading.

Accessibility

Follows the Button WAI-ARIA pattern. The accessible name is the visible text, or aria-label when the control is icon-only.

  • Decorative icons (the label already explains the action): aria-hidden="true".
  • Icon-only: aria-label on Button.
  • Loading: the original children stay in .sr-only; the spinner is aria-hidden.
  • disabled and isLoading take the control out of the tab order.

Keyboard support

KeyDescription
TabMove to the button.
Shift + TabMove to the previous control.
EnterActivate.
SpaceActivate.