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.
<button class="ht-btn ht-btn--primary ht-btn--md" type="button">
Save changes
</button>Variants
| 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
| 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
| 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.