Button

Action trigger. Two components: .lb-btn for buttons with text labels (with optional leading/trailing icon and count badge), and .lb-icon-btn for icon-only buttons that need an aria-label.

a11y (built in): both components are native <button> elements — tab order, Enter/Space activation and disabled semantics come from the platform, not from JS. Keyboard focus draws a :focus-visible outline in --lb-border-focus (mouse clicks stay clean), and every hover/pressed rule is guarded with :not(:disabled):not([aria-disabled="true"]), so both spellings of disabled read as inactive. Icon glyphs are aria-hidden="true"; .lb-icon-btn has no visible text so it always carries an aria-label, while the count pill is plain text inside the button and therefore joins its accessible name.

Usage: Primary for the main path (one per screen/group). Secondary for alternatives. Ghost for low-emphasis affordances. Danger for destructive actions. All slots compose freely — icon, label, and count badge can appear together.


Variants

Four visual variants — always paired with a size class (.lb-btn .lb-btn--secondary .lb-btn--medium); a bare .lb-btn renders collapsed. Pick by emphasis: primary for the main path, secondary for alternatives, ghost for low-emphasis or in-context actions, danger for destructive actions. --neutral and --subtle are back-compat aliases that render as ghost.

Sizes

Two sizes: medium (40px, default — pairs with inputs) and small (32px — for dense toolbars or inline actions).

Medium
Small

With leading icon

Icon BEFORE label — the most common composition. Use .lb-btn__icon as the first child. Reads as "icon + verb": "+ Add item", "✎ Edit". Reinforces the action's intent.

With trailing icon

Icon AFTER label — different intent. Use for "continue", "open menu", "expand": the icon points to where the action takes the user, not what the action does. Same .lb-btn__icon slot, just placed after the label text.

With count badge

Optional trailing pill carrying a number. Filters, notifications, "Reset (3)" affordances, multi-select counters. Uses the shared .lb-counter component with the --on-btn variant so the pill inherits the host button's variant colour pair (inverted contrast: pill bg = button text colour, digit = button bg colour). Empty counter auto-hides via :empty { display: none } — blank the textContent to remove the pill without rebuilding markup.

The third small button shows the empty-count behaviour — pair with disabled on the host button to make the control read as inactive.

With icon and count

All three slots together: leading icon, label, trailing count. Useful for filter pickers ("⚲ Issues 5"), inbox tabs ("📥 Inbox 12"), or any control that benefits from both an icon mnemonic and a numeric badge.

States

Buttons inherit hover, active, and focus styles automatically. The disabled attribute is the only state authors set explicitly — applies to all variants and all compositions. aria-disabled="true" renders identically and keeps the button in the tab order, but the CSS sets pointer-events: none on it too, so it is not a way to keep a disabled-looking button clickable.

Disabled — all variants
Disabled with icon and count

Icon-only — .lb-icon-btn

Separate component for icon-only buttons. Square shape, fixed box dimensions (40px medium, 32px small) with the icon glyph centered (24px medium, 16px small). Always require an aria-label — there's no visible text, so assistive tech needs the verb.

Medium — all variants
Small — all variants

Button group — .lb-btn-group

Layout wrapper that aligns multiple buttons with consistent spacing. Variants: --start, --end, --center, --justify (spread), --stack (vertical). Use for modal footers, toolbar actions, paired CTAs.

Start (left-aligned)
End (right-aligned — common for modal footers)