VayuUI

Button

An interactive element triggered by a user.

Installation

npx vayu-ui-cli@latest add button

Usage

Button Examples
Variants

Available button style variants for different use cases.

Sizes

Three sizes: small, medium (default), and large.

With Icons

Buttons with leading or trailing icons using compound subcomponents.

Loading State

Click the button to see the loading, success, and idle states cycle.

With Badges

Buttons with badge indicators for notifications and counts.

Disabled

Disabled buttons are non-interactive with reduced opacity.

<Button variant="primary">Primary</Button>
<Button variant="secondary">Secondary</Button>
<Button variant="outline">Outline</Button>
<Button variant="ghost">Ghost</Button>
<Button variant="destructive">Destructive</Button>

<Button size="small" variant="secondary">Small</Button>
<Button size="medium" variant="secondary">Medium</Button>
<Button size="large" variant="secondary">Large</Button>

<Button variant="primary">
  <Button.Icon><Mail /></Button.Icon>
  <Button.Text>Email Login</Button.Text>
</Button>

<Button variant="outline" size="large">
  <Button.Text>Send</Button.Text>
  <Button.Icon size="large"><Send /></Button.Icon>
</Button>

<Button
  variant="primary"
  loading={Status.PENDING}
  loadingText="Sending..."
>
  Click to Load
</Button>

<Button variant="secondary">
  <Button.Icon><Bell /></Button.Icon>
  <Button.Badge value={3} variant="danger" />
</Button>

<Button variant="outline">
  <Button.Text>Messages</Button.Text>
  <Button.Badge value={12} max={9} variant="primary" position="top-right" />
</Button>

<Button variant="primary" disabled>Disabled</Button>
<Button variant="primary" fullWidth>Full Width Button</Button>

Anatomy

<Button variant="primary" size="medium">
  <Button.Icon>
    <Mail />
  </Button.Icon>
  <Button.Text>Button Label</Button.Text>
  <Button.Badge value={3} variant="danger" />
</Button>
  • Button — root element, renders a native <button>
  • Button.Icon — wraps an icon element with sized constraints
  • Button.Text — wraps button label text with truncation
  • Button.Badge — notification badge positioned relative to the button

Accessibility

  • Keyboard Navigation: Activated via Enter and Space keys
  • Focus Visible: Displays a focus-visible ring using design tokens when navigating with keyboard
  • ARIA Attributes: Sets aria-disabled, aria-busy, and aria-label as needed
  • Loading State: Announced via aria-live="polite" with descriptive loading text
  • Semantic HTML: Renders a native <button> with the correct type attribute
  • Ref Forwarding: Supports programmatic focus through ref

Screen reader behavior

  • In default state, the screen reader announces the button's text content or aria-label
  • When disabled, the button is announced as "dimmed" or "unavailable" depending on the screen reader
  • During loading (Status.PENDING), aria-busy="true" signals an in-progress operation, and the aria-live="polite" region announces the loadingText value
  • Button.Badge uses role="status" with aria-live="polite" to announce count changes (e.g., "3 notifications")
  • Button.Icon is hidden from screen readers by default (aria-hidden="true"); set the label prop to make it accessible as an image
  • When loading transitions to Status.SUCCESS or Status.REJECTED, the button returns to its default content and the screen reader re-announces the visible text

Component Folder Structure

Button/
├── Button.tsx        # Root button component with loading states
├── ButtonIcon.tsx    # Icon wrapper subcomponent
├── ButtonBadge.tsx   # Notification badge subcomponent
├── ButtonText.tsx    # Text wrapper subcomponent
├── types.ts          # Shared types, enums, and interfaces
├── index.ts          # Public API and compound component export
└── README.md         # Component-level documentation

Props

Button

PropTypeDefaultDescription
variant"primary" | "secondary" | "outline" | "ghost" | "destructive""primary"Visual style of the button
size"small" | "medium" | "large""small"Size of the button
loadingStatusStatus.IDLELoading state controlling spinner and text
fullWidthbooleanfalseWhether the button spans full container width
loadingTextstring"Loading"Text displayed during loading state
disabledbooleanfalseDisables the button
type"button" | "submit" | "reset""button"Native HTML button type
aria-labelstringAccessible label when no visible text is present

Button.Icon

PropTypeDefaultDescription
size"small" | "medium" | "large""small"Size of the icon container
childrenReactNodeThe icon element to render
labelstringAccessible label; makes the icon visible to screen readers

Button.Text

PropTypeDefaultDescription
childrenReactNodeText content of the button
wrapbooleanfalseWhen true, disables the default truncation so long labels can wrap.

Button.Badge

PropTypeDefaultDescription
valuenumber | stringBadge content — number or short label
maxnumber99Maximum number before truncating with +
variant"primary" | "danger" | "warning" | "info" | "success""danger"Visual style of the badge
position"top-right" | "top-left" | "inline-right" | "inline-left""top-right"Position relative to the button
size"small" | "medium" | "large""small"Size of the badge
showZerobooleanfalseWhether to render the badge when value is 0

Wrapping Long Labels

Button.Text truncates by default. For labels that should wrap instead of showing an ellipsis, use the wrap prop:

<Button variant="primary">
  <Button.Text wrap>Very long label that should wrap to multiple lines</Button.Text>
</Button>

Status

ValueDescription
Status.IDLEDefault state, no loading indicator
Status.PENDINGShows spinner and loading text
Status.SUCCESSOperation completed (returns to normal state)
Status.REJECTEDOperation failed (returns to normal state)

On this page