Buttons · variants · sizes · loading · groups

Seven variants in four tones and three sizes — with icons, badges, keyboard shortcuts, async loading, toggles and attached groups.

Variants

Tones

Sizes & shapes

Icons, badges & shortcuts

Docs

Loading

Groups & toggles

bold on · italic off · underline off · view: list

Usage

JavaScript
import { signal } from "./index.js";import { Button, ButtonGroup } from "./components/button.js"; // variant: primary · secondary · soft · outline · ghost · plain · linkButton({ label: "Save", onClick: save })              // variant defaults to "primary"Button({ variant: "outline", tone: "danger", label: "Remove" })Button({ variant: "danger", icon: trashSvg, label: "Delete" }) // a tone as variant = solid // size: sm · md · lg — pill, block; an icon with no label is a square buttonButton({ size: "sm", pill: true, label: "Tag" })Button({ variant: "secondary", icon: moreSvg, ariaLabel: "More" }) // icons, a live badge, a keyboard shortcut (⌘S on a Mac, Ctrl+S elsewhere)Button({ icon: bellSvg, label: "Inbox", badge: () => unread() || null })Button({ label: "Save", shortcut: "mod+s", onClick: save })Button({ variant: "link", label: "Docs", href: "/docs", target: "_blank" }) // loading: an async onClick spins until it settles — or drive it yourselfButton({ label: "Save changes", onClick: async () => { await api.save(); } })Button({ label: "Export", loadingText: "Exporting…", onClick: exportCsv })Button({ label: "Manual", loading: busy })              // busy = signal(false) // toggles and groups: a signal as `pressed` is flipped on clickconst bold = signal(false);ButtonGroup({ attached: true, children: [  Button({ variant: "secondary", icon: boldSvg, ariaLabel: "Bold", pressed: bold }),  Button({ variant: "secondary", icon: italicSvg, ariaLabel: "Italic", pressed: italic }),] })