Skip to main content
The Button component supports six visual variants, six sizes, and an asChild prop that lets you render any element — such as a React Router <Link> — with button styles.

Import

import { Button, buttonVariants } from '@/components/ui/button'

Basic usage

<Button>Click me</Button>

Variants

Use the variant prop to control the visual style of the button.
<Button variant="default">Default</Button>
VariantDescription
defaultPrimary action. Filled with bg-primary.
destructiveDestructive or irreversible actions. Filled with bg-destructive.
outlineSecondary action with a visible border and transparent background.
secondaryLower-emphasis action. Filled with bg-secondary.
ghostNo background; hover shows bg-accent. Useful for inline or toolbar buttons.
linkRenders as styled text with an underline on hover. No background or border.

Sizes

Use the size prop to control the button’s dimensions.
<Button size="default">Default</Button>
SizeHeightPadding
defaulth-9px-4 py-2
smh-8px-3
lgh-10px-6
iconsize-9 (square)
icon-smsize-8 (square)
icon-lgsize-10 (square)

The asChild prop

Set asChild to true to merge the button’s props and styles onto its immediate child element instead of rendering a <button> tag. This uses the Radix UI Slot primitive. This is the recommended pattern for rendering a button that navigates to a route:
import { Link } from 'react-router-dom'
import { Button } from '@/components/ui/button'

<Button asChild size="lg">
  <Link to="/features">Explore Features</Link>
</Button>

<Button asChild variant="outline" size="lg">
  <Link to="/about">About Template</Link>
</Button>
The rendered output is an <a> element (from React Router’s <Link>) that carries all button Tailwind classes. No nested <button> is created.
When using asChild, the child element must accept className and event handler props. React Router’s <Link> and standard HTML elements both satisfy this requirement.

buttonVariants utility

Use the exported buttonVariants function to apply button styles to elements that you cannot wrap with <Button>, such as elements rendered by third-party libraries.
import { buttonVariants } from '@/components/ui/button'
import { cn } from '@/lib/utils'

<a
  href="/docs"
  className={cn(buttonVariants({ variant: 'outline', size: 'sm' }))}
>
  Read the docs
</a>
buttonVariants is produced by class-variance-authority and accepts the same variant and size options as the <Button> component.

Props

All native <button> HTML attributes are accepted via prop spreading. The component-specific props are:
variant
string
default:"default"
Visual style. One of default, destructive, outline, secondary, ghost, link.
size
string
default:"default"
Dimensions. One of default, sm, lg, icon, icon-sm, icon-lg.
asChild
boolean
default:"false"
When true, renders the Radix Slot component, merging button props onto the child element instead of creating a <button> tag.
className
string
Additional Tailwind classes merged via cn() (clsx + tailwind-merge). Use this to extend or override default styles.
disabled
boolean
Disables the button. Applies pointer-events-none and opacity-50 via Tailwind. Passed through to the underlying element.

Real-world examples

Ghost button to clear a search field

{query && (
  <Button variant="ghost" onClick={() => setQuery('')}>
    Clear
  </Button>
)}
import { Link } from 'react-router-dom'
import { Button } from '@/components/ui/button'

<Button asChild size="lg">
  <Link to="/features">Explore Features</Link>
</Button>

<Button asChild variant="outline" size="lg">
  <Link to="/about">About Template</Link>
</Button>

Accessibility

  • Focus ring — A visible ring appears on keyboard focus via focus-visible:ring-ring/50 focus-visible:ring-[3px], satisfying WCAG 2.4.7.
  • Disabled statedisabled:pointer-events-none disabled:opacity-50 prevents interaction and provides a visual cue.
  • Invalid statearia-invalid on the button triggers a ring-destructive outline, useful for form error states.
  • Icon buttons — Always provide an aria-label when using size="icon", size="icon-sm", or size="icon-lg" because there is no visible text label.

Build docs developers (and LLMs) love