Skip to content
Design System
Contact

Button

Actions in six variants and four sizes, with loading and icon-only forms.

Components<button>cva

Playground

Change the props and the preview and JSX update together. Ink is the primary action; brand violet is reserved for AI actions.

Preview

Props

Size
Icon
example.tsxtsx
1import { Button } from "@/components/ui/button"
2
3<Button>
4 <Plus />
5 Add project
6</Button>

Variants and sizes

One primary action per view. Outline and ghost carry secondary actions; destructive is tinted rather than solid so it never outshouts the primary.

All variants
default
outline
secondary
ghost
destructive
link
Brand

States

Hover and focus are simulated with the same classes the pseudo-classes apply, so every state can be compared at rest.

States
  • Defaultrest
  • Hover:hover
  • Focus:focus-visible
  • Loadingaria-busy
  • Disableddisabled
  • Erroraria-invalid
  • Successdata-state=success

Patterns

Real compositions: an async button that walks through loading, success and failure, grouped actions and icon buttons with tooltips.

Async action

Click to run the async flow

Icon toolbar with tooltips
Split button
Dialog footer and link
Read the API

API

PropTypeDefaultDescription
variant"default" | "outline" | "secondary" | "ghost" | "destructive" | "link""default"Visual weight. Use one default (ink) button per view.
size"xs" | "sm" | "default" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"Height 24–36px. Icon sizes are square and need an aria-label.
asChildbooleanfalseRender the styles on a child element, e.g. a Next.js <Link>.
data-icon"inline-start" | "inline-end"—Set on an icon child to tighten the padding on that side.
aria-busyboolean—Set while an async action runs, together with disabled.

Accessibility

Keyboard

Move focus to the button
Tab
Activate
EnterorSpace

Semantics and ARIA

  • Renders a native <button>, so role, focus and activation come for free. Use asChild with a link for navigation.
  • Icon-only buttons require an aria-label; pair them with a tooltip that shows the same text.
  • Loading sets aria-busy and disabled and keeps the label, so the width stays stable and the announcement stays meaningful.
  • Focus uses :focus-visible with a 3px ring at 50% brand, which keeps 3:1 contrast against the sheet in both themes.
  • Default height is 32px; 24px extra-small buttons are for dense toolbars only, with at least 8px between targets.