Skip to content

Button

A button with solid, outline, ghost, and soft variants, plus intent colors, sizes, a loading state, and icon slots.

Import

import { Button } from "@hyzer-labs/ui"

Demo

Four looks for one control. solid (the default) fills with the intent color, outline and ghost lighten the footprint, and soft tints the intent into the surface. Each row shows one variant across a few intents.

solid
outline
ghost
soft
<!-- solid (default) -->
<Button>primary</Button>
<Button intent="secondary">secondary</Button>
<Button intent="danger">danger</Button>

<!-- outline -->
<Button variant="outline">primary</Button>
<Button variant="outline" intent="secondary">secondary</Button>
<Button variant="outline" intent="danger">danger</Button>

<!-- ghost -->
<Button variant="ghost">primary</Button>
<Button variant="ghost" intent="secondary">secondary</Button>
<Button variant="ghost" intent="danger">danger</Button>

<!-- soft -->
<Button variant="soft">primary</Button>
<Button variant="soft" intent="secondary">secondary</Button>
<Button variant="soft" intent="danger">danger</Button>

Props

NameTypeDefaultNote
variant'solid' | 'outline' | 'ghost' | 'soft''solid'
intentIntent'primary'The full intent registry, not a hand-picked subset — see Foundation → Colors & Intent.
size'sm' | 'md' | 'lg' | 'full''md'full fills its container at the md height/padding; not combinable with sm/lg.
disabledbooleanfalse
loadingbooleanfalse
loadingLabelstring'Loading'Screen-reader-only text announced while loading; not rendered visually.
hrefstringRenders as a plain <a> (no role="button") when set — it genuinely navigates.
type'button' | 'submit' | 'reset''button'
ariaLabelstringRequired for icon-only buttons.
onclick(e: MouseEvent) => voidSwallowed while disabled or loading.
classstringMerged after the hz-button class.
childrenSnippet
iconStartSnippetAn icon snippet with no children renders the compact circular icon-only form.
iconEndSnippet

Theme hooks

What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.

Root class: .hz-button

Data attributes

HookValuesStyles
data-variant'solid' | 'outline' | 'ghost' | 'soft'Default solid.
data-intent'neutral' | any registered intentThe full vocabulary, not a hand-picked subset — register an intent and Button stamps it. Sets --hz-button-accent.
data-size'sm' | 'md' | 'lg' | 'full'Default md. full fills its container at the md height/padding — not combinable with sm/lg.
data-state'disabled' | 'loading' | absentAbsent when interactive — every hover and active rule is gated on :not([data-state]), so styling a state cannot be undone by a hover. disabled wins over loading.
data-icon-onlypresent when icon-onlyDerived: an icon with no children. Renders the circle form.

Custom properties

HookValuesStyles
--hz-button-accent<color> — default var(--hz-intent-primary)The accent every variant derives from. Override it to restyle solid, outline, ghost, and soft at once — and it is how you wire up an intent you registered yourself.
--hz-button-on-accent<color> — default var(--hz-color-surface)Text colour on a solid fill. The surface role rather than white, so it flips with the mode and keeps solid text ≥ 4.5:1 in both.
--hz-button-tint<percentage> — 14% light, 28% darkSoft-variant background tint strength — the same recipe and hook pattern as --hz-badge-tint.

Accessibility

Use ariaLabel for icon-only buttons (no visible text). The loading state sets aria-busy="true" and renders a screen-reader-only "Loading" label. Disabled state sets aria-disabled="true".

References: APG Button pattern · MDN: <button>