Skip to content

Components · Actions

Button

Primary, secondary, ghost and danger actions; loading and icon-only forms.

@rookcom/monarch-reactComponent

Variants

Usage examples

Sizes, icons, loading

As a link

Status and brand

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. success and warning buttons are for actions whose outcome is that status ("Approve", "Mark at risk").
  • The choice is on/off: use Switch or Checkbox.

Design principles

  • One primary button per view region; everything else is secondary or ghost.
  • danger is for the final, destructive confirmation; danger-subtle for destructive actions that still need confirming.
  • Labels are verbs that say what happens ("Save changes", not "OK").
  • Never submits by accident: the default type is button.
  • success and warning are status actions, used sparingly next to a neutral alternative.
  • brand is 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';
ExportKindRenderingRoot class
Buttoncomponentclient.mn-theme
IconButtoncomponentclient.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.

PropTypeDescription
asChildbooleanRender the child element (e.g. a Next.js <Link>) with button styling.
blockbooleanStretch to the container width.
brandColorstringBrand 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.
childrenReactNode
classNamestring
disabledboolean
idstring
leadingIconReactNodeIcon before the label. Decorative: hidden from assistive technology.
loadingbooleanShows a spinner, sets aria-busy and blocks clicks while keeping focus.
sizeButtonSize
trailingIconReactNode
variantButtonVariantVisual weight. Use one `primary` per view region. Default `secondary`.

IconButton

Square button containing only an icon. `label` is required by the type system.

PropTypeDescription
icon requiredReactNode
label requiredstringRequired accessible name, since there is no visible text.
asChildbooleanRender the child element (e.g. a Next.js <Link>) with button styling.
brandColorstringBrand 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.
classNamestring
disabledboolean
idstring
loadingbooleanShows a spinner, sets aria-busy and blocks clicks while keeping focus.
sizeButtonSize
variantButtonVariantVisual weight. Use one `primary` per view region. Default `secondary`.

Variants

PropValuesDescription
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

StateExposed asBehaviour
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.
Disableddisabled, aria-disabled="true"Native disabled plus aria-disabled; 50% opacity and a not-allowed cursor.
Loadingdata-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 disabled or loading, clicks are prevented and onClick is not called.
  • With asChild, the child element (for example a router link) receives the classes, data attributes and handlers; no type attribute is added.
  • leadingIcon and trailingIcon render in decorative spans.

Keyboard support

KeysAction
TabMoves focus to the button, including while it is loading.
Enter / SpaceActivates the button.

Accessibility

Role
button (or the child element’s role with asChild)
Accessible name
The visible label; IconButton requires label, which becomes aria-label.
  • Icons are wrapped in aria-hidden spans, so they never add to the accessible name.
  • Loading sets aria-busy="true" and aria-disabled="true" instead of disabled, so focus is not lost.
  • IconButton cannot be rendered without a label: the type requires it.
  • brand text 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.
  • block makes 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.
  • primary uses the product accent (--mn-color-accent-solid), so each Rook product gets its own accent with no code change.
  • danger variants use the danger tokens, which do not change with the product accent; success and warning likewise use their status tokens.
  • brand carries one derived colour set per theme and picks the one matching .dark / data-mn-theme / data-mn-contrast on 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, not disabled.
  • Use asChild to 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 danger for actions that are reversible.
  • Do not pass a brand colour through style or className: use variant="brand" + brandColor so contrast is checked.
  • 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

KindDepends on
npm packages@radix-ui/react-slot@rookcom/monarch-tokensreact
Monarch componentsSpinner

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 a type attribute.
  • Keeps the child element and spinner with asChild + loading, and both icons with asChild.
  • Hides decorative icons from assistive technology.
  • IconButton uses label as its accessible name and shows only the spinner while loading.
  • success and warning expose their variant; xs its size.
  • brand derives 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.
CheckCovered
Axe in jsdomYes
Server renderingYes
Browser suitesVisual baselines: /__gallery; Cross-browser axe: /components/button

Tests: packages/react/src/components/actions/button