Button
stable 3.4 kB<hk-button>The most-used control in any library, so the surface is deliberately small: every combination of variant, tone, size and shape resolves to token lookups rather than a hand-written skin. Works as a <button> or an <a>, and handles the two states buttons usually get wrong — a loading button that keeps its width, and a disabled anchor that actually stops navigating. It can also carry a badge: inline, where the button grows to fit it, or floating over the corner for an icon button, with the count collapsing past 99 so the button never widens.
#button#action#cta#glass
Live demo
Running in this page — interact with it.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| variant | 'solid' | 'soft' | 'outline' | 'ghost' | 'link' | 'glass' | 'solid' | Visual treatment. Glass adds a blurred, saturated backdrop. |
| tone | 'brand' | 'neutral' | 'success' | 'warning' | 'danger' | 'brand' | Semantic colour. Resolves the accent tokens the variant paints with. |
| size | 'xs' | 'sm' | 'md' | 'lg' | 'md' | Height, padding and font size. |
| shape | 'rounded' | 'pill' | 'square' | 'rounded' | Corner radius. |
| type | 'button' | 'submit' | 'reset' | 'button' | Native type. Ignored on an anchor. |
| disabled | boolean | false | Blocks interaction. Uses aria-disabled on an anchor, which cannot be natively disabled. |
| loading | boolean | false | Shows a spinner, blocks the press, and sets aria-busy. The label stays in place. |
| block | boolean | false | Stretches to the container width. |
| iconOnly | boolean | false | Square padding for an icon. Requires an aria-label. |
| badge | string | number | null | null | Count or short label carried by the button. `null` and a numeric 0 render nothing. |
| badgePosition | 'inline' | 'corner' | 'inline' | Inline sits in the flex flow, so the button grows to fit it. Corner floats over the top-right for icon buttons, and is the one case the button stops clipping. |
| badgeTone | 'auto' | 'contrast' | 'brand' | 'neutral' | 'success' | 'warning' | 'danger' | 'auto' | Auto reads the variant: a filled button gets a contrast chip so the badge does not vanish into the fill. |
| badgeMax | number | 99 | Counts above this collapse to "99+", so the badge cannot widen the button. The true value is still what gets announced. |
| shine | boolean | null | null | Pointer-tracking specular highlight. Defaults on for glass, off elsewhere. |
Outputs
pressedEventEmitter<MouseEvent>Emits on click. Never fires while disabled or loading.
Keyboard
- Space / Enter Presses the button
- Tab Moves focus in and out
Theming
Component tokens, each falling back to a global one:
| Property | Default | Controls |
|---|---|---|
| --hk-btn-accent | #dc2626 | Fill for solid, text for the quiet variants. |
| --hk-btn-accent-strong | #b91c1c | Hover fill. |
| --hk-btn-accent-soft | rgb(220 38 38 / 0.1) | Wash behind soft and ghost hover. |
| --hk-btn-radius | 10px | Corner radius. Shape overrides it. |
| --hk-btn-ring | var(--hk-btn-accent) | Focus ring colour. |
Accessibility
- A disabled anchor gets aria-disabled and a prevented default — an <a> ignores the disabled attribute and would still navigate.
- Loading sets aria-busy and blocks the click, so a double submit cannot slip through between renders.
- The label stays in the DOM while loading, so the accessible name never disappears and the button does not change width.
- An icon-only button has no text, so it requires an aria-label; the spinner ships a visually hidden "Loading" for the same reason.
- Focus ring is a 2px offset outline that survives every variant, including glass.