Loading page…
Loading page…
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.
Four variants map to four levels of commitment. A screen gets at most one primary.
import { Button } from "@coldharbor/webui";
<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-focussm for dense toolbars, md as the default, lg for ceremony moments, icon for square glyph-only actions.
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-focusHover, 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.
Static replica of the :hover classes + 1.02 scale.
Static replica of the global focus outline.
whileTap press scale 0.93, held statically.
Static replica of the :hover classes + 1.02 scale.
Static replica of the global focus outline.
whileTap press scale 0.93, held statically.
Static replica of the :hover classes + 1.02 scale.
Static replica of the global focus outline.
whileTap press scale 0.93, held statically.
Static replica of the :hover classes + 1.02 scale.
Static replica of the global focus outline.
whileTap press scale 0.93, held statically.
<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-focusOne action rail: first button cuts bottom-left, last cuts top-right, and internal seams stay square. Single-item groups keep both signature cuts.
<ButtonGroup aria-label="Wallet actions">
<Button variant="outline">Receive</Button>
<Button variant="outline">Swap</Button>
<Button>Send</Button>
</ButtonGroup>--ch-primary--ch-primary-fg--ch-surface--ch-border--ch-fg--ch-fg-muted--ch-focusEvery 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.
Disabled + aria-busy while in flight.
import { StatefulButton } from "@coldharbor/webui";
<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-focusMagnetic pull and ripple are live pointer behaviors — move the cursor over them. Both collapse under reduced motion.
Cursor-attracted; strength 0.25 default.
Anchor with button skin — external explorer links.
ripple spawns from the press point; press to see it.
import { ButtonLink, MagneticButton } from "@coldharbor/webui";
<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-focusThe 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