Button

An action. Use a <button>, or an <a href> styled as a button when the action is navigation. For navigation inside text, use Link.

Installation

bun add @harmo-ui/css @harmo-ui/react

Usage

import { Button } from "@harmo-ui/react";

<Button>Save changes</Button>

Examples

This site is using your theme

Variants

Primary for the main action, secondary for the others, and tertiary for the least prominent.

<>
  <Button>Save changes</Button>
  <Button variant="secondary">Cancel</Button>
  <Button variant="tertiary">Show details</Button>
</>

Small

data-size="small": 29px tall with 13px text, for notices, table rows, and cards.

<>
  <Button size="small">Save changes</Button>
  <Button variant="secondary" size="small">
    Cancel
  </Button>
  <Button variant="tertiary" size="small">
    Show details
  </Button>
</>

Disabled

disabled dims a button and takes it out of the tab order. aria-disabled="true" looks the same but stays focusable, so a tooltip can say why: Tab reaches the last button here.

<>
  <Button disabled>Save changes</Button>
  <Button variant="secondary" disabled>
    Cancel
  </Button>
  <Button variant="tertiary" disabled>
    Show details
  </Button>
  <Button size="small" disabled>
    Save changes
  </Button>
  <Button aria-disabled="true">Publish</Button>
</>

When the action is navigation, put .hbutton on an <a href>: it keeps link behavior, such as opening in a new tab, with a button's look.

<>
  <Button href="/campaigns/new">Create campaign</Button>
  <Button variant="secondary" href="/reports">
    View reports
  </Button>
  <Button variant="tertiary" href="/help">
    Read more
  </Button>
</>

Icon only

Without a label, a button is a square as wide as it is tall, with its icon drawn at 20px. aria-label gives it the name a label would. Where the icon alone might not be clear, a Tooltip can show that name too.

<>
  <Button variant="tertiary" aria-label="Close">
    <CloseIcon className="hicon" />
  </Button>
  <Button variant="secondary" aria-label="More actions">
    <MoreIcon className="hicon" />
  </Button>
  <Button aria-label="Add contact">
    <PlusIcon className="hicon" />
  </Button>
  <Button variant="secondary" size="small" aria-label="More actions">
    <MoreIcon className="hicon" />
  </Button>
  <Button aria-label="Add contact" disabled>
    <PlusIcon className="hicon" />
  </Button>
</>

Side by side

Labeled and icon-only buttons share --harmo-control-height, so they line up in a row.

<>
  <Button variant="secondary">Export</Button>
  <Button variant="secondary" aria-label="More actions">
    <MoreIcon className="hicon" />
  </Button>
  <Button>Save changes</Button>
</>

With an arrow

A ChevronToArrow turns into an arrow on hover or keyboard focus. A back arrow leads the label and a forward arrow follows it; the button balances the space on the other side.

All reports
<>
  <Button variant="secondary">
    <ChevronToArrow direction="back" />
    Go back
  </Button>
  <Button>
    Continue
    <ChevronToArrow />
  </Button>
  <Button variant="tertiary" href="/reports">
    All reports
    <ChevronToArrow />
  </Button>
</>

Right to left

The same buttons in Arabic. Arrows follow the reading direction: back points right and forward points left.

<>
  <Button variant="secondary">
    <ChevronToArrow direction="back" />
    رجوع
  </Button>
  <Button>
    متابعة
    <ChevronToArrow />
  </Button>
</>

Long labels

In a narrow container, drawn dashed here, a long label wraps and the button grows taller instead of overflowing.

<>
  <Button>Save changes and notify everyone</Button>
  <Button variant="secondary">Export every conversation</Button>
</>

Loading

aria-busy with aria-disabled keeps a loading button focusable, and a spinner follows its label or takes its icon's place. Press the last button to watch it load for two seconds.

const [saving, setSaving] = useState(false);
const save = () => {
  setSaving(true);
  setTimeout(() => setSaving(false), 2000);
};

return (
  <>
    <Button loading>Saving changes</Button>
    <Button variant="secondary" loading>
      Exporting
    </Button>
    <Button aria-label="Adding contact" loading>
      <PlusIcon className="hicon" />
    </Button>
    <Button loading={saving} onClick={save}>
      Save changes
    </Button>
  </>
);

API reference

React

<Button> renders a <button type="button">, or with href a link, with its text in .hbutton-label. An icon as its first or last child stays outside the label: Harmo's <ChevronToArrow>, <Chevron>, or <ExpandChevron>, or any element with the hicon class. Other props are the button's or the link's attributes, so type="submit" makes a submit button.

PropEffect
variantsecondary or tertiary; primary when left out
sizesmall
loadingShows the spinner after the label, sets aria-busy and aria-disabled, and ignores clicks
hrefRenders a link, through HarmoProvider's linkComponent (an <a> by default)
aria-disabled"true" keeps it focusable but ignores clicks, so a Tooltip can explain why

HTML

A <button class="hbutton"> or <a class="hbutton">, with its text in .hbutton-label. Load button.css; spinner.css for loading; and chevron-to-arrow.css, expand-chevron.css, or tooltip.css when used.

HookEffect
(default)Primary: filled --harmo-color-primary
data-variant="secondary"Outlined, lightly raised
data-variant="tertiary"Text only; tinted background on hover
data-size="small"Compact: 29px, 13px text, 6px radius, for notices, rows, and cards
disabledDimmed, not-allowed cursor (<button> only)
aria-disabled="true"Looks disabled but stays focusable; a script must ignore activation
aria-busy="true"Loading, with aria-disabled="true" and a .hspinner

A script is only needed for aria-disabled and loading. See Button's full contract.

Accessibility

  • aria-disabled="true" instead of disabled keeps the button focusable, so a tooltip can explain why it's unavailable.
  • A loading button keeps focus, so a keyboard user who submitted with Enter doesn't lose their place.
  • An icon-only button needs a name: aria-label, or a Tooltip referenced with aria-labelledby. A naming tooltip uses aria-labelledby rather than aria-describedby, so screen readers say "Close, button" once, not twice.