Wuko
Components

Button

Triggers an action. Buttons are how users commit to a primary, contextual, or destructive intent. Pick the variant that matches the weight of the action.

Installation

terminal
npx shadcn@latest add @wuko/button

Usage

components/example.tsx
import { Button } from "@/components/ui/button";

export function Example() {
  return <Button variant="primary">Deploy</Button>;
}

Variants

Five variants cover the full hierarchy from this is the action down to subtle, contextual, optional.

tsx
<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="danger">Danger</Button>

Sizes

tsx
<Button size="sm">Small</Button>
<Button size="md">Medium</Button>
<Button size="lg">Large</Button>

States

disabled blocks interaction; loading shows a spinner before the children, sets aria-busy, and disables the button.

tsx
<Button>Default</Button>
<Button disabled>Disabled</Button>
<Button loading>Saving…</Button>

Composing with icons

Place any icon (or other element) inside the button as a child. Button uses flex with a size-aware gap, so spacing matches the button's size automatically.

tsx
import { ArrowRight, Trash2, Zap } from "lucide-react";

<Button>
  <Zap className="size-4" />
  Deploy
</Button>
<Button variant="outline">
  Continue
  <ArrowRight className="size-4" />
</Button>
<Button variant="danger">
  <Trash2 className="size-4" />
  Delete project
</Button>

Icon button

Square sizes for icon-only triggers: icon-xs, icon-sm, icon, and icon-lg. An aria-label is required so the button has an accessible name.

tsx
<Button size="icon-xs" aria-label="Deploy">
  <Zap className="size-3.5" />
</Button>
<Button size="icon-sm" aria-label="Deploy">
  <Zap className="size-4" />
</Button>
<Button size="icon" aria-label="Deploy">
  <Zap className="size-4" />
</Button>
<Button size="icon-lg" aria-label="Deploy">
  <Zap className="size-5" />
</Button>

Props

PropTypeDefaultDescription
variant"primary" | "secondary" | "outline" | "ghost" | "danger""primary"Visual hierarchy of the button.
size"sm" | "md" | "lg" | "icon-xs" | "icon-sm" | "icon" | "icon-lg""md"Controls dimensions, padding, and font size. The icon-* sizes are square. Use them for icon-only buttons with an aria-label.
loadingbooleanfalseShows a spinner before the children, sets aria-busy, and disables interaction.
disabledbooleanfalsePrevents user interaction via the native disabled attribute.
...restButtonHTMLAttributes<HTMLButtonElement>All native button attributes are forwarded to the element.

Accessibility

  • Renders a native <button> element. Tab focus, Enter, and Space activation work without extra wiring.
  • The loading state sets aria-busy="true" and disables interaction; the spinner SVG is aria-hiddenso screen readers announce the button text (e.g. “Saving…”) as the progress indicator.
  • disabled uses the native attribute, which removes the element from the tab order and blocks click events.
  • Focus is announced visually with a 2px outline in --wuko-accent at a 2px offset, visible against any background.
  • Icon-only buttons (any size="icon-*") require an aria-labelso screen readers can announce the button's purpose. The icon SVG itself should remain aria-hidden.