Button
An interactive element triggered by a user.
Installation
npx vayu-ui-cli@latest add buttonUsage
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
EnterandSpacekeys - Focus Visible: Displays a
focus-visiblering using design tokens when navigating with keyboard - ARIA Attributes: Sets
aria-disabled,aria-busy, andaria-labelas needed - Loading State: Announced via
aria-live="polite"with descriptive loading text - Semantic HTML: Renders a native
<button>with the correcttypeattribute - 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 thearia-live="polite"region announces theloadingTextvalue Button.Badgeusesrole="status"witharia-live="polite"to announce count changes (e.g., "3 notifications")Button.Iconis hidden from screen readers by default (aria-hidden="true"); set thelabelprop to make it accessible as an image- When
loadingtransitions toStatus.SUCCESSorStatus.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 documentationProps
Button
| Prop | Type | Default | Description |
|---|---|---|---|
variant | "primary" | "secondary" | "outline" | "ghost" | "destructive" | "primary" | Visual style of the button |
size | "small" | "medium" | "large" | "small" | Size of the button |
loading | Status | Status.IDLE | Loading state controlling spinner and text |
fullWidth | boolean | false | Whether the button spans full container width |
loadingText | string | "Loading" | Text displayed during loading state |
disabled | boolean | false | Disables the button |
type | "button" | "submit" | "reset" | "button" | Native HTML button type |
aria-label | string | — | Accessible label when no visible text is present |
Button.Icon
| Prop | Type | Default | Description |
|---|---|---|---|
size | "small" | "medium" | "large" | "small" | Size of the icon container |
children | ReactNode | — | The icon element to render |
label | string | — | Accessible label; makes the icon visible to screen readers |
Button.Text
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | — | Text content of the button |
wrap | boolean | false | When true, disables the default truncation so long labels can wrap. |
Button.Badge
| Prop | Type | Default | Description |
|---|---|---|---|
value | number | string | — | Badge content — number or short label |
max | number | 99 | Maximum 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 |
showZero | boolean | false | Whether 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
| Value | Description |
|---|---|
Status.IDLE | Default state, no loading indicator |
Status.PENDING | Shows spinner and loading text |
Status.SUCCESS | Operation completed (returns to normal state) |
Status.REJECTED | Operation failed (returns to normal state) |