A flexible button component with variants, sizes, loading and disabled states.
A flexible button component with variants, sizes, loading and disabled states.
Variants
1<div style={{ display: "flex", gap: "8px" }}>2 <Button variant="primary">Primary</Button>3 <Button variant="secondary">Secondary</Button>4 <Button variant="ghost">Ghost</Button>5 <Button variant="danger">Danger</Button>6</div>
Sizes
1<div style={{ display: "flex", gap: "8px", alignItems: "center" }}>2 <Button size="sm">Small</Button>3 <Button size="md">Medium</Button>4 <Button size="lg">Large</Button>5</div>
States
1<div style={{ display: "flex", gap: "8px" }}>2 <Button loading>Loading</Button>3 <Button disabled>Disabled</Button>4</div>
Icon-only
For buttons whose content is a single icon (glyph, <svg>, etc.) with no visible label, pass iconOnly and provide an aria-label for accessibility.
1<Button iconOnly aria-label="Delete" variant="danger">2 ✕3</Button>
Label colour on raised surfaces
A ghost or outlined (secondary) button draws its label in the primary role, which measures 4.02:1 on the-behive's Dark surface-container-high (a Modal or dialog panel), under the 4.5:1 small-text rule. The default tokens give 8.43:1 there, so only brands whose primary is darker are affected. The label reads the --button-label-color custom property (default primary), so a surface sets it on its own class instead of selecting into Button's classes, the same way Card reads --card-border:
1.my-dialog-actions {2 --button-label-color: var(--md-sys-color-secondary);3}
With the-behive's tokens, secondary on surface-container-high measures 12.87:1 in Dark and 5.40:1 in Light. The ghost and secondary variants read the property; primary and danger ignore it (their label is on-primary / on-error on a filled container). ConfirmDialog sets it for its Cancel button.
Inside a Modal or Drawer: their content portals to body, so a property set on a wrapper around <Modal> never reaches the buttons. Set it through the component's className (it lands on the dialog content inside the portal) or on a class inside the body:
1<Modal className="my-modal" title="Image details" onClose={close}>2 …3</Modal>
1.my-modal {2 --button-label-color: var(--md-sys-color-secondary);3}
Examples
Open all 9 →1const PrimaryButton = () => <Button variant="primary">Primary</Button>;23render(<PrimaryButton />);
1const SecondaryButton = () => (2 <Button variant="secondary">Secondary</Button>3);45render(<SecondaryButton />);
Properties
Button props
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | boolean | false | Renders the button's props onto its single child element (via Radix `Slot`) instead of a `<button>` |
children | ReactNode | — | Button label/content |
className | string | — | Additional class names to merge with the component root element |
iconOnly | boolean | false | Renders the button as a square icon-only control with no visible label spacing |
loading | boolean | false | Shows a spinner and disables the button while an action is in flight |
size | "sm" | "md" | "lg" | md | Controls the size; defaults to md |
variant | "primary" | "secondary" | "ghost" | "danger" | primary | Controls the color/style treatment; defaults to primary |
Also accepts the native attributes of its root element (className, aria-*, event handlers, …).