Button

The action a person takes on a view.

Overview

An action that changes something in place: save, cancel, delete, retry.

One primary

Do
import { Button, ButtonGroup } from "@hackcove/ui";

<ButtonGroup>
  <Button>Save</Button>
  <Button variant="outline">Cancel</Button>
</ButtonGroup>
One primary action per view: the quieter variants carry the rest.
Don't
import { Button, ButtonGroup } from "@hackcove/ui";

<ButtonGroup>
  <Button>Save</Button>
  <Button>Save and close</Button>
</ButtonGroup>
Two primaries compete for the eye, and nobody can tell which action the view is about.

Disabled reason

Do
import { Button } from "@hackcove/ui";

<Button disabledReason="Pick a shop first">Save</Button>
disabledReason greys the button and shows the reason on hover.
Don't
import { Button } from "@hackcove/ui";

<Button disabled>Save</Button>
A bare disabled leaves the user guessing why nothing happens.

Navigate

Do
import { ButtonLink } from "@hackcove/ui";

<ButtonLink href="#quotes" variant="outline">
  Open quotes
</ButtonLink>
Going to another page is a link: ButtonLink keeps the anchor, middle-click and the URL.
Don't
import { Button } from "@hackcove/ui";

<Button variant="outline" onClick={() => location.assign("#quotes")}>
  Open quotes
</Button>
A button that sets location loses middle-click, the status-bar URL and crawlability.

Destructive

Do
import { Button, ButtonGroup } from "@hackcove/ui";

<ButtonGroup>
  <Button tone="danger">Delete quote</Button>
  <Button variant="outline">Cancel</Button>
</ButtonGroup>
A destructive action is tone="danger", so the red says what the button does before it is read.
Don't
import { Button, ButtonGroup } from "@hackcove/ui";

<ButtonGroup>
  <Button>Delete quote</Button>
  <Button variant="outline">Cancel</Button>
</ButtonGroup>
A delete in the primary look reads as the safe, expected action; the tone is what warns.

Row

Do
import { Button, ButtonGroup, ButtonLink } from "@hackcove/ui";

<ButtonGroup
  label="Quote actions"
  end={
    <ButtonLink href="#quotes" variant="ghost">
      Back
    </ButtonLink>
  }
>
  <Button>Save</Button>
  <Button variant="outline">Preview</Button>
</ButtonGroup>
A ButtonGroup spaces and wraps its buttons, and end pushes one item to the far side.
Don't
Back
import { Button, ButtonLink } from "@hackcove/ui";

<div>
  <Button>Save</Button>
  <Button variant="outline">Preview</Button>
  <ButtonLink href="#quotes" variant="ghost">
    Back
  </ButtonLink>
</div>
Buttons loose in a div have no gap, do not wrap as one row, and the group has no name.

Usage

  • Put one primary Button in a view. The other actions use variant="outline", "ghost" or "link"; variant="dashed" is the full-width "add another" row at the foot of a list.
  • Group the actions in a ButtonGroup, so the row spaces and wraps the same everywhere; end pushes one item, such as a Back link, to the far side, and label names the group.
  • Say why a button cannot be pressed with disabledReason; use busy while the work is in flight.
  • Going to another page is a ButtonLink, not a Button.
  • A destructive action is tone="danger", an accept is tone="positive"; size="sm" is for a button inside a row or a card header.
  • pressed makes a toggle; busyLabel replaces the label while busy.
  • An icon-only action is an IconButton, not a Button holding a glyph.

Accessibility

  • A Button is a native <button> and defaults to type="button", so it never submits a form by accident.
  • disabledReason disables the button and sets the reason as its title, so a pointer user sees why.
  • busy sets aria-busy and disables the button until the work is done.
  • Every Button has a focus ring from the component, so the keyboard focus is always visible.

API

Button

A button in the primary, outline, ghost, link or dashed style (the link has no box: text with an underline on hover, for an in-line action; dashed is the full-width "add" row, a dashed outline with muted text that fills its container, with an optional leading icon in the children). size="sm" is compact; tone="positive" is green and tone="danger" is red; pressed makes it a toggle with a selected look. Defaults to type="button". disabledReason and busy are the two disabled states that say more than a plain disabled, which keeps its half-opacity look.


busy

The work is in flight: disabled, aria-busy, keeps its colour. Wins over disabledReason.

Type
boolean
Default
false

busyLabel

The label while busy, in place of the children.

Type
string

disabledReason

Why the button cannot be pressed: disables it, sets it as the title, and greys it flat.

Type
string

pressed

A toggle: sets aria-pressed, and when true draws the selected look (the tone's border and tint) on the outline and ghost variants. Left out, the button is not a toggle and has no aria-pressed.

Type
boolean

size

sm is compact, for table rows and toolbars.

Type
"sm" | "md"
Default
md

tone

positive is green, for accept and approve; danger is red, for delete and clear. Not for dashed, which has the one look.

Type
"default" | "positive" | "danger"
Default
default

variant
Type
"link" | "primary" | "outline" | "ghost" | "dashed"
Default
primary

ButtonLink

A link that looks like a Button. A plain <a href> by default; with render it draws the app's own router <Link> instead (render={(p) => <Link to="/quotes" {...p} />}), already carrying the button classes. variant, size and tone are the Button's.


external

Opens in a new tab: adds target="_blank" and rel="noreferrer".

Type
boolean
Default
false

href
Type
string

render
Type
LinkRender

size

sm is compact, for table rows and toolbars.

Type
"sm" | "md"
Default
md

tone

positive is green, for accept and approve; danger is red, for delete and clear. Not for dashed, which has the one look.

Type
"default" | "positive" | "danger"
Default
default

variant
Type
"link" | "primary" | "outline" | "ghost" | "dashed"
Default
primary

ButtonGroup

A wrapping row of buttons with one optional item pushed to the far end (a Back link, say). On a narrow viewport the row wraps and end stays at the end of the last line. label makes it a named role="group". Directly in a Card footer it spans the footer, so end reaches the far edge.


end

One item, pushed to the end of the row.

Type
ReactNode

label

The accessible name of the group: sets role="group" and aria-label.

Type
string

Functions

  • buttonClass The Button's look as a class string, for a router link that has to look like a Button when ButtonLink's render cannot draw it.