Button
The Button component allows users to trigger actions, submit forms, and navigate through the application. The button component supports various combinations of variants, themes, sizes, and states.
Default Button
The default button with no specified variant or theme.
Installation
import { Button } from '@harnessio/ui/components'
// Default button<Button>Default Button</Button>
// Primary button with themes<Button variant="primary" theme="default">Primary Button</Button><Button variant="primary" theme="success">Success Button</Button><Button variant="primary" theme="danger">Danger Button</Button>
// Secondary button<Button variant="secondary">Secondary Button</Button>
// Outline button with themes<Button variant="outline">Outline Button</Button><Button variant="outline" theme="success">Success Outline</Button><Button variant="outline" theme="danger">Danger Outline</Button>
// Ghost button<Button variant="ghost">Ghost Button</Button>
// AI button (a special variant for AI-related actions)<Button variant="ai">AI Button</Button>
// Link button (note: doesn't accept theme prop)<Button variant="link">Link Button</Button>
// Transparent button (note: doesn't accept theme prop)<Button variant="transparent">Transparent Button</Button>
// Button sizes<Button>Medium Size Button (default)</Button><Button size="sm">Small Button</Button><Button size="xs">Extra Small Button</Button>
// Loading state<Button loading>Loading Button</Button>
// Disabled state<Button disabled>Disabled Button</Button>Primary Button
Primary buttons are the most prominent buttons in the interface.
Secondary Button
Secondary buttons are less prominent than primary buttons.
Outline Button
Outline buttons have a transparent background with a visible border.
Ghost Button
Ghost buttons are minimally styled and blend into the background when not interacted with.
AI Button
AI variant buttons are used specifically for AI-related actions.
Link Button
Link buttons appear as text links and don’t accept theme props.
Transparent Button
Transparent buttons have no background or border and don’t accept theme props.
Promise callback
When the onClick prop returns a promise, the button automatically shows a loading state until the promise resolves or rejects.
Looking for icon-only buttons? See Icon Button.
Anatomy
A button is composed of a few parts:
- Container — the interactive surface. Its background, border, and radius are set by the
variantandtheme. - Label — the text describing the action. This is also the button’s accessible name.
- Icon (optional) — an
IconV2placed alongside the label, or on its own for icon-only buttons. - Loading spinner — replaces (or joins) the label while an action is in progress.
The size prop scales all of these together. The 2xs and 3xs sizes are icon-only by design and automatically drop the text label.
Best practices
Do
- Lead with one clear primary button per view so the main action is obvious.
- Write labels as specific actions — “Save changes”, “Delete project” — not vague ones like “OK” or “Submit”.
- Use
theme="danger"for destructive actions so the consequence reads at a glance. - Reach for
secondary,outline, orghostfor supporting actions that shouldn’t compete with the primary one. - Return a promise from
onClickfor async work — the button handles its own loading state and prevents double-clicks.
Don’t
- Don’t stack multiple primary buttons next to each other; it removes the visual hierarchy.
- Don’t use a button for plain navigation to another page — use a Link (or
asChildwith an<a>) so it stays a real link. - Don’t rely on an icon alone without a label or tooltip to convey meaning.
- Don’t disable a button without making it clear why, or how the user can enable it.
Content
- Use sentence case for every button label — capitalize only the first word, along with any proper nouns or product names.
- Keep labels short — ideally one or two words.
- Start with a verb: “Add member”, “Copy link”, “Run pipeline”.
- Match the label to the outcome so it reads as a promise of what happens next.
- Avoid punctuation and articles (“a”, “the”) unless they’re needed for clarity.
Accessibility
Keyboard
- The button is a native
<button>, so it’s focusable in tab order and activates with both Enter and Space — no extra wiring needed. - Disabled and loading buttons are removed from interaction (they can’t be clicked or activated).
Labeling
- The text inside the button is its accessible name. For icon-only buttons there’s no text, so an accessible name is required — the component enforces this at the type level by requiring
tooltipPropswheniconOnlyis set. You can opt out withignoreIconOnlyTooltipif you provide your ownaria-label. - Any ARIA attributes you pass (
aria-label,aria-describedby,aria-expanded,aria-pressed, …) are forwarded to the underlying element.
Focus & states
- A visible focus ring appears only for keyboard focus (
:focus-visible), not on mouse click, using the--cn-focustoken. - The disabled state uses the native
disabledattribute rather than styling alone, so assistive tech reports it correctly.
Known limitations
- The loading state is not announced to screen readers — it sets
disabledand shows a spinner, but there’s noaria-busyor live-region text. If it’s important that a screen-reader user knows an action is in progress, add your ownaria-livemessaging. - The icon-only label requirement is enforced by TypeScript only; a plain-JavaScript consumer can still render an icon-only button with no accessible name.
Properties
Prop | Required | Default | Type |
|---|---|---|---|
| variant | false | 'primary' | 'primary' | 'secondary' | 'outline' | 'ghost' | 'link' | 'transparent' |
| type | false | 'button' | 'button' | 'submit' | 'reset' |
| theme | false | 'default' | 'default' | 'success' | 'danger' | 'warning' |
| size | false | 'md' | 'md' | 'sm' | 'xs' | '2xs' | '3xs' |
| rounded | false | false | boolean |
| iconOnly | false | false | boolean |
| loading | false | false | boolean |
| disabled | false | false | boolean |
| asChild | false | false | boolean |
| className | false | string | |
| children | true | ReactNode | |
| tooltipProps | false | undefined | Pick<TooltipProps, 'title' | 'content' | 'side'> |
| ignoreIconOnlyTooltip | false | undefined | boolean |
Type Constraints
The Button component implements a discriminated union pattern with conditional prop requirements:
- When variant is “ghost” or “link”, no theme is allowed
- When variant is “ai”, no theme is needed
- When variant is “solid”, themes “success” and “danger” are not allowed
This ensures type safety and provides proper development-time feedback on invalid component usage.