Skip to content

Button

One button family, four variants, one async arc. Primary carries the single decisive action per screen; secondary and outline support it; ghost recedes. StatefulButton owns every network round-trip — broadcast, swap, restore — so loading and outcome states are never improvised.

Variants

Four variants map to four levels of commitment. A screen gets at most one primary.

Primary
Secondary
Ghost
Outline
import { Button } from "@coldharbor/ui";

<Button variant="primary">Send Bitcoin</Button>
<Button variant="secondary">View details</Button>
<Button variant="ghost">Dismiss</Button>
<Button variant="outline">Copy address</Button>
  • --ch-primary
  • --ch-primary-fg
  • --ch-surface
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-focus

Sizes

sm for dense toolbars, md as the default, lg for ceremony moments, icon for square glyph-only actions.

sm
md (default)
lg
icon

Square, rounded-lg. Always give it an aria-label.

<Button size="sm">Copy txid</Button>
<Button size="lg">Create wallet</Button>
<Button size="icon" aria-label="Show QR code">…</Button>
  • --ch-primary
  • --ch-primary-fg
  • --ch-surface
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-focus

Interaction states — every variant

Hover, focus and active are pointer/keyboard states, so they are forced here with static classes and transforms to keep every cell visible at once. Hover also lifts scale to 1.02 and active presses to 0.93 via Motion springs at runtime.

Primary

Default
Hover (forced)

Static replica of the :hover classes + 1.02 scale.

Focus-visible (forced)

Static replica of the global focus outline.

Active (forced)

whileTap press scale 0.93, held statically.

Disabled

Secondary

Default
Hover (forced)

Static replica of the :hover classes + 1.02 scale.

Focus-visible (forced)

Static replica of the global focus outline.

Active (forced)

whileTap press scale 0.93, held statically.

Disabled

Ghost

Default
Hover (forced)

Static replica of the :hover classes + 1.02 scale.

Focus-visible (forced)

Static replica of the global focus outline.

Active (forced)

whileTap press scale 0.93, held statically.

Disabled

Outline

Default
Hover (forced)

Static replica of the :hover classes + 1.02 scale.

Focus-visible (forced)

Static replica of the global focus outline.

Active (forced)

whileTap press scale 0.93, held statically.

Disabled
<Button variant="primary" disabled>Send Bitcoin</Button>
{/* hover/focus/active are pointer states — never hard-code them in product code */}
  • --ch-primary
  • --ch-primary-fg
  • --ch-surface
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-focus

StatefulButton — the async arc

Every network action renders all four states through the same control: the label cascades out, the icon slot widens in. While loading the button is disabled and aria-busy.

idle
loading

Disabled + aria-busy while in flight.

success
error
Swap confirm — loading
Swap confirm — error
import { StatefulButton } from "@coldharbor/ui";

<StatefulButton
  state={broadcastState} // "idle" | "loading" | "success" | "error"
  loadingText="Broadcasting…"
  successText="Broadcast"
  errorText="Try again"
>
  Send Bitcoin
</StatefulButton>
  • --ch-primary
  • --ch-primary-fg
  • --ch-surface
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-focus

Magnetic pull and ripple are live pointer behaviors — move the cursor over them. Both collapse under reduced motion.

MagneticButton (live)

Cursor-attracted; strength 0.25 default.

ButtonLink

Anchor with button skin — external explorer links.

Ripple (live)

ripple spawns from the press point; press to see it.

import { ButtonLink, MagneticButton } from "@coldharbor/ui";

<MagneticButton strength={0.25}>Restore wallet</MagneticButton>
<ButtonLink href="/explorer/tx/…">View on explorer</ButtonLink>
<Button ripple>Verify backup</Button>
  • --ch-primary
  • --ch-primary-fg
  • --ch-surface
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-focus

Themes

The hero pairing — one primary action with its secondary escape — under all three layers.

  • --ch-primary
  • --ch-primary-fg
  • --ch-surface
  • --ch-border
  • --ch-fg
  • --ch-fg-muted
  • --ch-focus