Capítulo 20 de 108

Button

Core Idea

Button is the base interactive element: 6 variants, 8 sizes, and composable icon/spinner slots via data-icon attributes for correct spacing.

Key Concepts

  • variant: default | outline | ghost | destructive | secondary | link.
  • size: default | xs | sm | lg | icon | icon-xs | icon-sm | icon-lg (icon sizes are square, for icon-only buttons).
  • data-icon="inline-start" / "inline-end": required attribute on any icon or Spinner placed inside a Button for correct spacing, not automatic just by being a child.
  • buttonVariants helper: use to style a plain <a> as a link that looks like a button; never render Button as an <a> via render/nativeButton={false}, since the Base UI Button forces role="button" which overrides the native link semantics.
  • Cursor behavior: Tailwind v4 changed buttons to cursor: default; opt into cursor: pointer via CSS (@layer base rule) or shadcn init --pointer.

Code Examples

<Button variant="outline">Button</Button>
<Button variant="outline" size="icon" aria-label="Submit">
  <ArrowUpIcon />
</Button>
<Button variant="outline">
  <IconGitBranch data-icon="inline-start" /> New Branch
</Button>
<Button variant="outline">
  Fork
  <IconGitFork data-icon="inline-end" />
</Button>
  • O que demonstra: posicionamento correto de ícone antes/depois do texto via data-icon.
<Button variant="outline" disabled>
  <Spinner data-icon="inline-start" />
  Generating
</Button>
  • O que demonstra: estado de loading com Spinner, mesmo padrão de data-icon que ícones normais.
import { buttonVariants } from "@/components/ui/button"

<a href="#" className={buttonVariants({ variant: "secondary", size: "sm" })}>
  Login
</a>
  • O que demonstra: link estilizado como botão sem violar semântica de acessibilidade.
@layer base {
  button:not(:disabled),
  [role="button"]:not(:disabled) {
    cursor: pointer;
  }
}
  • O que demonstra: como restaurar cursor: pointer (Tailwind v4 default mudou para cursor: default).

Reference Tables

PropTypeDefault
variant"default" | "outline" | "ghost" | "destructive" | "secondary" | "link""default"
size"default" | "xs" | "sm" | "lg" | "icon" | "icon-xs" | "icon-sm" | "icon-lg""default"

Anti-patterns

  • <Button render={<a />} nativeButton={false} /> for links: Base UI's Button always sets role="button", breaking the anchor's native link semantics for assistive tech. Use buttonVariants on a real <a> instead.
  • Icon/spinner children without data-icon: spacing will be wrong; the attribute is what triggers the correct margin/gap.
  • Assuming cursor: pointer by default: Tailwind v4 changed this; must opt in explicitly (per user's own global convention: resolve once in @layer base, never per-class).

Key Takeaways

  1. Icon-only buttons must set aria-label (no visible text for screen readers to use).
  2. RTL: use rtl:rotate-180 on directional icons (e.g. arrows) and dir={dir} on the container; see the RTL configuration guide for global setup.
  3. Button composes with ButtonGroup for grouped action clusters (separate component/docs).
  4. Manual install requires @base-ui/react as a dependency (this is a Base UI-backed component, base: base style).

Connects To

  • components-button-group: for grouping multiple buttons together.
  • components-input-group: shares similar data-icon-style slot conventions for inline addons.