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

NameTypeDefaultDescription
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.
disabledbooleanfalseBlocks interaction. Uses aria-disabled on an anchor, which cannot be natively disabled.
loadingbooleanfalseShows a spinner, blocks the press, and sets aria-busy. The label stays in place.
blockbooleanfalseStretches to the container width.
iconOnlybooleanfalseSquare padding for an icon. Requires an aria-label.
badgestring | number | nullnullCount 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.
badgeMaxnumber99Counts above this collapse to "99+", so the badge cannot widen the button. The true value is still what gets announced.
shineboolean | nullnullPointer-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:

PropertyDefaultControls
--hk-btn-accent#dc2626Fill for solid, text for the quiet variants.
--hk-btn-accent-strong#b91c1cHover fill.
--hk-btn-accent-softrgb(220 38 38 / 0.1)Wash behind soft and ghost hover.
--hk-btn-radius10pxCorner radius. Shape overrides it.
--hk-btn-ringvar(--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.

More in Forms