Components · Actions
Button
Primary, secondary, ghost and danger actions; loading and icon-only forms.
Variants
import { Button } from '@rookcom/monarch-react';
<Button variant="primary">Save changes</Button>
<Button>Cancel</Button>
<Button variant="ghost">Learn more</Button>
<Button variant="danger">Delete</Button>
<Button variant="danger-subtle">Remove</Button>Usage examples
Sizes, icons, loading
import { Button, IconButton } from '@rookcom/monarch-react';
// MoreIcon, PlusIcon: demo icons from the docs site; use your own.
<Button size="xs">Extra small</Button>
<Button size="sm">Small</Button>
<Button size="md" leadingIcon={<PlusIcon />}>
New ticket
</Button>
<Button size="lg" variant="primary" loading>
Saving
</Button>
<Button disabled>Disabled</Button>
<IconButton label="More actions" icon={<MoreIcon />} />As a link
import { Button } from '@rookcom/monarch-react';
<Button asChild variant="primary">
<a href="#/components/button">Open ticket</a>
</Button>Status and brand
import { Button } from '@rookcom/monarch-react';
<Button variant="success">Approve</Button>
<Button variant="warning">Mark at risk</Button>
<Button variant="brand" brandColor="#0f766e">
Book a call
</Button>
<Button variant="brand" brandColor="#facc15">
Join waitlist
</Button>
<Button variant="success" size="xs">
Approve
</Button>Purpose
Triggers an action in place: saving, submitting, opening a dialog or menu. IconButton is the square, icon-only form.
Use when
- The user performs an action on the current view (save, delete, open a menu).
- A link must look like a button, for example a primary "New ticket" call to action (use
asChild).
Avoid when
- The control navigates as ordinary text: use Link.
- Status is only being shown, not acted on: use Badge.
successandwarningbuttons are for actions whose outcome is that status ("Approve", "Mark at risk"). - The choice is on/off: use Switch or Checkbox.
Design principles
- One
primarybutton per view region; everything else issecondaryorghost. dangeris for the final, destructive confirmation;danger-subtlefor destructive actions that still need confirming.- Labels are verbs that say what happens ("Save changes", not "OK").
- Never submits by accident: the default
typeisbutton. successandwarningare status actions, used sparingly next to a neutral alternative.brandis for a tenant or customer colour (a hosted booking page, a public form). Contrast is guaranteed by the tokens engine, never by the caller.
API reference
import { Button, IconButton } from '@rookcom/monarch-react';| Export | Kind | Rendering | Root class |
|---|---|---|---|
Button | component | client | .mn-theme |
IconButton | component | client | .mn-theme |
Both components forward their ref to the underlying <button> (or the child element with asChild).
All native button attributes pass through.
Generated from the TypeScript declarations of packages/react/src/components/actions/button.
Properties
Button
The primary action control. Defaults to `type="button"` so a button inside a form never submits by accident; pass `type="submit"` explicitly.
| Prop | Type | Description |
|---|---|---|
asChild | boolean | Render the child element (e.g. a Next.js <Link>) with button styling. |
block | boolean | Stretch to the container width. |
brandColor | string | Brand or tenant colour for `variant="brand"`, as `#rrggbb` or `#rgb`. The fill and text are derived per theme by the tokens contrast engine, so the label always meets WCAG AA (7:1 in more-contrast themes); the colour may be darkened or lightened to get there. Ignored by other variants. An invalid or missing colour falls back to the product accent. |
children | ReactNode | |
className | string | |
disabled | boolean | |
id | string | |
leadingIcon | ReactNode | Icon before the label. Decorative: hidden from assistive technology. |
loading | boolean | Shows a spinner, sets aria-busy and blocks clicks while keeping focus. |
size | ButtonSize | |
trailingIcon | ReactNode | |
variant | ButtonVariant | Visual weight. Use one `primary` per view region. Default `secondary`. |
IconButton
Square button containing only an icon. `label` is required by the type system.
| Prop | Type | Description |
|---|---|---|
icon required | ReactNode | |
label required | string | Required accessible name, since there is no visible text. |
asChild | boolean | Render the child element (e.g. a Next.js <Link>) with button styling. |
brandColor | string | Brand or tenant colour for `variant="brand"`, as `#rrggbb` or `#rgb`. The fill and text are derived per theme by the tokens contrast engine, so the label always meets WCAG AA (7:1 in more-contrast themes); the colour may be darkened or lightened to get there. Ignored by other variants. An invalid or missing colour falls back to the product accent. |
className | string | |
disabled | boolean | |
id | string | |
loading | boolean | Shows a spinner, sets aria-busy and blocks clicks while keeping focus. |
size | ButtonSize | |
variant | ButtonVariant | Visual weight. Use one `primary` per view region. Default `secondary`. |
Variants
| Prop | Values | Description |
|---|---|---|
variant | 'primary''secondary''ghost''danger''danger-subtle''success''warning''brand' | Visual weight. Default secondary; IconButton defaults to ghost. Exposed as data-variant. success and warning use the status tokens; brand uses brandColor. |
brandColor | '#rrggbb''#rgb' | The tenant colour for variant="brand". Fill, hover, active and text are derived for light, dark and both more-contrast themes with deriveIntent from @rookcom/monarch-tokens, so text meets 4.5:1 (7:1 in more contrast). Sets data-brand and inline --_brand-* properties. Missing or invalid: the product accent is used. |
size | 'xs''sm''md''lg' | Control height from the --mn-control-* tokens. Default md. Exposed as data-size. |
block | 'true''false' | Stretches to the container width (data-block). |
States
| State | Exposed as | Behaviour |
|---|---|---|
| Hover and active | – | Background steps through the variant’s hover and active tokens. |
| Focus | – | Visible focus ring from --mn-color-focus-ring on :focus-visible only. |
| Disabled | disabled, aria-disabled="true" | Native disabled plus aria-disabled; 50% opacity and a not-allowed cursor. |
| Loading | data-loading, aria-busy="true", aria-disabled="true" | Replaces the leading icon with a small Spinner, blocks clicks, and stays focusable so keyboard users keep their place. |
Interaction behaviour
- Click, Enter and Space call
onClick(native button behaviour). - While
disabledorloading, clicks are prevented andonClickis not called. - With
asChild, the child element (for example a router link) receives the classes, data attributes and handlers; notypeattribute is added. leadingIconandtrailingIconrender in decorative spans.
Keyboard support
| Keys | Action |
|---|---|
| Tab | Moves focus to the button, including while it is loading. |
| Enter / Space | Activates the button. |
Accessibility
- Role
- button (or the child element’s role with asChild)
- Accessible name
- The visible label; IconButton requires
label, which becomesaria-label.
- Icons are wrapped in
aria-hiddenspans, so they never add to the accessible name. - Loading sets
aria-busy="true"andaria-disabled="true"instead ofdisabled, so focus is not lost. - IconButton cannot be rendered without a
label: the type requires it. brandtext contrast is computed, not assumed: a light brand colour gets dark text, a mid colour is darkened until white text passes.
Responsive behaviour
- Heights come from the
--mn-control-*tokens and do not change with viewport width. blockmakes the button fill narrow containers such as mobile dialog footers.- Labels do not wrap (
white-space: nowrap).
Theme support
- Colours are Monarch semantic tokens only, so light, dark and more-contrast themes apply automatically.
primaryuses the product accent (--mn-color-accent-solid), so each Rook product gets its own accent with no code change.dangervariants use the danger tokens, which do not change with the product accent;successandwarninglikewise use their status tokens.brandcarries one derived colour set per theme and picks the one matching.dark/data-mn-theme/data-mn-contraston an ancestor, so a tenant colour also adapts to dark and more-contrast themes.
Do and don't
Do
- Use one primary button per region, placed where the eye ends (the right of a dialog footer).
- Keep the button focusable while work is in progress: use
loading, notdisabled. - Use
asChildto style a router link as a button instead of nesting a button in a link.
Don't
- Do not put several primary buttons side by side.
- Do not use an IconButton for an action whose icon is ambiguous; give it a visible label instead.
- Do not use
dangerfor actions that are reversible. - Do not pass a brand colour through
styleorclassName: usevariant="brand"+brandColorso contrast is checked.
Related components
- Link: Navigation that reads as text.
- DropdownMenu: Several related actions behind one button.
- Spinner: Loading indicator the button uses internally.
- Toolbar: Groups buttons with arrow-key navigation.
Dependencies
| Kind | Depends on |
|---|---|
| npm packages | @radix-ui/react-slot@rookcom/monarch-tokensreact |
| Monarch components | Spinner |
Testing coverage
- Defaults to
type="button". - Activates with click, Enter and Space.
- Stays focusable but inert while loading (aria-busy, aria-disabled, no click).
- Disables natively when
disabled. - Renders the child element with
asChild, without atypeattribute. - Keeps the child element and spinner with
asChild+loading, and both icons withasChild. - Hides decorative icons from assistive technology.
- IconButton uses
labelas its accessible name and shows only the spinner while loading. successandwarningexpose their variant;xsits size.brandderives a fill and text for all four themes that meet 4.5:1 (7:1 in more contrast) for light, mid and dark brand colours, accepts#rgb, keeps caller styles, and falls back to the accent for an invalid colour.
| Check | Covered |
|---|---|
| Axe in jsdom | Yes |
| Server rendering | Yes |
| Browser suites | Visual baselines: /__gallery; Cross-browser axe: /components/button |
Tests: packages/react/src/components/actions/button