Button
Button
Triggers an action when clicked. Buttons should describe the action they perform — prefer Save changes over OK.
When to use
- For actions in forms, dialogs, and toolbars.
- Use
<a>(or aLinkcomponent) for navigation, even if it visually looks like a button.
Anatomy
- Container — the
<button>element. Receives the variant/tone/size data attributes that drive styling. - Label — the children. May be a string, an icon, or both. Icons use the
gapfrom--helixui-space-2.
Variants
variant controls the visual emphasis. Pair it with tone to convey intent.
<Button variant="solid">Solid</Button><Button variant="soft">Soft</Button><Button variant="ghost">Ghost</Button><Button variant="outline">Outline</Button>Tones
tone controls the color, independent of variant. The same solid button can be brand, neutral, or danger.
<Button tone="brand">Save</Button><Button tone="neutral">Cancel</Button><Button tone="danger">Delete</Button>Sizes
<Button size="sm">Small</Button><Button size="md">Medium</Button><Button size="lg">Large</Button>Disabled
<Button disabled>Cannot click</Button>Forwarding refs and props
Button is React.forwardRef<HTMLButtonElement> and forwards every standard <button> attribute. This includes onClick, aria-*, form, name, etc.
const ref = useRef<HTMLButtonElement>(null);<Button ref={ref} onClick={handle} aria-describedby="hint" />Do / Don’t
- Do use one primary (
solid+brand) action per view. - Do put destructive actions on the left and confirmation on the right in dialogs (or follow your platform convention).
- Don’t use
disabledto hide functionality — explain why the action is unavailable. - Don’t stack multiple
solidbrandbuttons next to each other; demote secondary actions tosoft,ghost, oroutline.
Install: @helixui/core
import { Button } from '@helixui/core'status: stable · since: 0.1.0
Tags: clickable, action, primary, destructive, submit, cta
Live playground
Open the full editor or source on GitHub.
Anatomy
┌────────────────────────────────┐│ [icon?] Label [icon?] │ ← children = label, optional leading/trailing icon└────────────────────────────────┘ ↑ background per (variant, tone) ↑ border per (variant, tone) ↑ rounded by radius.mdLayout
- display —
inline-block - width —
content - height —
content - intrinsicSize —
~28px (sm) / 36px (md) / 44px (lg) tall, hugs label width - stackable —
true - fullBleed —
false
Visual
A pill-shaped, slightly rounded rectangle (radius.md). solid fills with the tone color and uses on-tone text;
soft is a tinted fill of the tone with the tone’s text color; outline is transparent with a 1px tone border;
ghost is fully transparent until hover. Hover and active states deepen the tone by one step. The focus ring
is a 2px offset outline on :focus-visible only. Disabled buttons are 50% opacity and lose pointer events.
Props
| Name | Type | Default | Description |
|---|---|---|---|
variant | 'solid' | 'soft' | 'ghost' | 'outline' | solid | Visual style. solid is the highest-emphasis option; ghost the lowest. |
tone | 'brand' | 'neutral' | 'danger' | brand | Color intent. brand for primary actions, neutral for secondary, danger for destructive. |
size | 'sm' | 'md' | 'lg' | md | Button size. Use sm in compact UIs (toolbars), lg for prominent calls to action. |
disabled | boolean | false | Disables interaction. Removes the element from the tab order. |
type | 'button' | 'submit' | 'reset' | button | Native HTML button type. Defaults to button so the component does not accidentally submit forms. |
...rest | ButtonHTMLAttributes<HTMLButtonElement> | — | All standard button attributes are forwarded to the underlying <button>. |
Tokens used
color.bg.action.brand.default, color.bg.action.brand.hover, color.bg.action.brand.active, color.bg.action.brand.subtle, color.bg.action.neutral.default, color.bg.action.neutral.hover, color.bg.action.neutral.active, color.bg.action.danger.default, color.bg.action.danger.hover, color.bg.action.danger.active, color.bg.action.danger.subtle, color.bg.surface.default, color.text.on.brand, color.text.on.danger, color.text.primary, color.text.action.brand, color.text.action.danger, color.border.default, color.border.strong, color.border.focus, color.border.danger, radius.md, space.2, space.3, space.4, space.5, font.size.sm, font.size.md, font.size.lg, font.family.sans, font.weight.semibold, font.lineHeight.tight
Accessibility
Role: button
Keyboard
| Key | Action |
|---|---|
Space | Activates the button. |
Enter | Activates the button. |
Notes
- Always provide an accessible name via children or
aria-label. Icon-only buttons must usearia-label. - Prefer
aria-disabled="true"overdisabledwhen the action is conditionally unavailable but should remain focusable so users can discover it. - The focus ring is rendered with
outlineand only on:focus-visible, so mouse users do not see it.
Composes with
| Component | Relation | Note |
|---|---|---|
IconButton | alternative | Use IconButton when there is no text label. |
Form | parent | type="submit" submits the surrounding Form. |
DialogTrigger | child | Common opener for dialogs. |
Stack | parent | Group multiple buttons in a Stack with direction="row". |
Tooltip | wraps | Wrap with Tooltip when the action needs explanation. |
Prompt examples
These are the AI prompt → JSX mappings used by the helixui prompt DSL and integrations like Cursor / Claude Code.
primary call-to-action
“add a Save button”
<Button>Save</Button>destructive action
“make a Delete button”
<Button tone="danger">Delete</Button>secondary action next to a primary
“Cancel + Save buttons in a row”
<Stack direction="row" gap={2} justify="end"> <Button variant="ghost" tone="neutral">Cancel</Button> <Button>Save</Button></Stack>icon-only action in a toolbar
“small ghost button with just a search icon”
<Button variant="ghost" size="sm" aria-label="Search"> <SearchIcon /></Button>form submission
“submit button at the bottom of a form”
<Button type="submit" size="lg">Create account</Button>