Skip to content
Halbton v0.1.0
Concept project

Components

Button

The one component every system gets wrong by having too many of. Four variants, and each has a job you can state in a sentence.

When to use it

For an action that changes something. If it navigates, it is a link — use an anchor, so it opens in a new tab when someone middle-clicks it and appears where a screen reader expects a link to be.

When not to

  • Never for navigation. A button styled as a link and a link styled as a button are both fine; a button that navigates is not.
  • Not two primaries on one view. If two actions look equally important, the page has not decided what it is for. Pick one.
  • Not critical for emphasis. Critical means destructive. Using it to make something feel urgent trains people to ignore it when it matters.
  • Not disabled as a substitute for an explanation. A disabled button gives no reason and cannot be focused to ask for one. Leave it enabled and say what is missing when it is pressed.

Try it

The sample updates with the controls because both read the same state object. It is not possible for the code below to describe something other than what is rendered above it.

matches the preview above
<button class="ht-btn ht-btn--primary ht-btn--md" type="button">
  Save changes
</button>

Variants

Each variant, and the single job it has.
Variant Specimen Use it for
primary The one affirmative action on the view.
secondary Everything alongside a primary — cancel, back, a second path.
quiet Dense contexts: table rows, toolbars, anywhere a border would add noise.
critical Destructive actions only. Deleting, revoking, discarding.

Sizes

States

Props

Defaults are what you get by omitting the prop.
Prop Values Default Notes
variant primary · secondary · quiet · critical primary Primary is the one affirmative action on a view — if there are two, one of them is secondary. Critical is for destructive actions only; using it for emphasis trains people to ignore it.
size sm · md · lg md md is the default and covers almost everything. sm is for dense table rows; lg is for a single standalone action, not for making a button feel important.
disabled true · false false Prefer leaving the button enabled and explaining what is missing. A disabled control gives no reason and cannot be focused to ask for one.
loading true · false false Sets aria-busy and blocks repeat submission. The label stays in place so the button does not change width mid-action.
children text Save changes Say what happens, in the user's words. 'Save changes' beats 'Submit'; 'Delete project' beats 'OK'.

Keyboard

Native button behaviour, not reimplemented.
Key Result
Tab Moves focus to the button. Disabled buttons are skipped.
Space Activates on key up, matching native button behaviour.
Enter Activates on key down.

Accessibility

  • It is a real <button>. Keyboard activation, focus order and the accessibility tree all come free, and none of it is reimplemented.
  • type="button" is always set. The default is submit, which silently submits any form the button happens to be inside.
  • Loading sets aria-busy and disables the control, and the label does not change — so the button does not resize mid-action and shift the layout under the pointer.
  • The focus ring is a dedicated token held to 3:1 against every surface a button can sit on, not just the page canvas.