Skip to content

Button

Runs an action or submits a form. Primary is ink, not violet: the accent is kept for focus, links and the lamp.

PrimitivesServer-rendered

Examples

Variants

One primary per view. Accent at most once per page. Ghost for toolbars and table rows.

Light
Dark
Example
<Button>Add server</Button>
<Button variant="accent">Connect agent</Button>
<Button variant="secondary">Test connection</Button>
<Button variant="ghost">Cancel</Button>
<Button variant="danger">Delete server</Button>
<Button variant="danger-ghost">Remove</Button>

Sizes and icons

Example
<Button size="sm" variant="secondary" icon="refresh">Refresh</Button>
<Button size="md" icon="plus">Add server</Button>
<Button size="lg" variant="secondary" iconEnd="external-link">Open docs</Button>
<IconButton icon="settings" label="Settings" />

Loading and disabled

Loading keeps the width and stays focusable; clicks are blocked and aria-busy is set.

Disabled link
Example
<Button loading icon="refresh">Testing…</Button>
<Button variant="secondary" disabled>Disabled</Button>
<Button href="/servers" variant="secondary" disabled>Disabled link</Button>

Usage

Usage
import { Button, IconButton } from '@switchboard/ui'

<form method="post" action={routes.servers.create.href()}>
  …
  <Button type="submit" icon="plus">Add server</Button>
</form>

Props

Every component also takes mix, for css() and mixins applied after its own style.

Button

Button props
PropTypeDefaultDescription
variant'primary' | 'accent' | 'secondary' | 'ghost' | 'danger' | 'danger-ghost''primary'Visual weight. Primary is ink.
size'sm' | 'md' | 'lg''md'Height 28, 32 or 36px.
iconIconName–Leading icon. Replaced by a spinner while loading.
iconEndIconName–Trailing icon.
iconOnlyboolean–Square button showing only the icon. Needs aria-label; prefer IconButton.
loadingboolean–Spinner, aria-busy, clicks blocked, width kept.
blockboolean–Stretch to the container width.
hrefstring–Renders an <a> styled as a button.
disabledboolean–On a link, drops the href and sets aria-disabled.
mixMix–Extra css() or mixins, applied after the button style.

Also accepts every <button> attribute, or every <a> attribute when href is set.

IconButton

IconButton props
PropTypeDefaultDescription
icon*IconName–The icon.
label*string–Accessible name and tooltip text.
variantButtonVariant'ghost'Same variants as Button.
tooltip'top' | 'bottom' | 'left' | 'right' | false'top'Tooltip placement, or false for none.

Accessibility

  • Renders a native <button type="button"> (or <a> with href), so Enter and Space work without JavaScript.
  • Icon-only buttons need a name: IconButton sets aria-label from `label` and shows it as a tooltip.
  • Loading sets aria-busy and keeps focus on the button; the spinner has no separate label.
  • Focus ring is the shared 2px accent ring with a page-coloured gap.

Guidance

Do

  • Name the action with a verb and its object: "Add server", "Delete skill".
  • Use one primary button per view; make the rest secondary or ghost.

Don’t

  • Use the accent variant as a general brand colour.
  • Put "Submit", "OK" or a trailing arrow on a button.