Brik Design System
Components

Button

Trigger an event or action. The button family includes three components for different semantic needs.

The button family is three components for three semantic needs. Pick the right one before you reach for variants — the wrong shell semantically can't be fixed with styling.

Button

Use when: The action stays on this page (submit, open dialog, confirm).

Don't: Use it for navigation — right-click and keyboard nav break.

<Button>Save</Button>

LinkButton

Use when: The action navigates to a URL. Renders an `<a>` element.

Don't: Wrap a Button in a link — that breaks accessibility.

<LinkButton href="/docs">…</LinkButton>

IconButton

Use when: There's no visible text label. Single-icon trigger.

Don't: Downgrade emphasis when converting from Button — keep the variant.

<IconButton icon={…} label="Close" />

Use it for

  • Submitting a form
  • Confirming or cancelling a destructive operation
  • Opening a sheet, dialog, or popover
  • Triggering an immediate, synchronous action

Import

import { Button, LinkButton, IconButton } from '@brikdesigns/bds';

Anatomy

  1. 1
    Container

    The hit target. Sets size, padding, focus ring, and base variant styles.

  2. 2
    Leading icon

    Optional icon before the label. Use 1em sizing to scale with text.

  3. 3
    Labelrequired

    The accessible name of the button. Required for Button and LinkButton.

  4. 4
    Trailing icon

    Optional icon after the label. Common for "Continue →" or external-link affordances.

Variants

Twelve variants. Pick by action emphasis and destructive intent, not by aesthetics.

Standard ladder

highest emphasis → lowest
1
primary

Main call-to-action. One per section.

2
outline

Secondary emphasis. The "alongside primary" choice.

3
secondary

Tertiary, subtle. Supporting actions that don't compete.

4
ghost

Minimal emphasis. Repeated actions, navigation, dismiss.

Destructive

negative is the destructive action — confirm dialogs, delete-and-go flows.

The danger / danger-outline / danger-ghost trio that used to sit below it was retired in #1957: it carried the brand accent-red rather than the system negative, and had zero call sites across every consumer repo. The retired spellings still resolve to negative for one minor and warn once in the console.

Lower-emphasis destructive (an outline or ghost button that is also negative) is not expressible today, because variant carries form and valence on one axis. Tracked in #1983.

State variants

positive confirms a completed action. inverse and on-color are for placement on dark or branded surfaces. To mark the active option in a group, pass the boolean selected prop on top of any variant — it is a state modifier, not a variant value.

Sizes

Five sizes. md is the default. Use sm only inside dense surfaces (table rows, popovers); use lg/xl for primary CTAs on landing pages.

Icons

Use iconBefore and iconAfter props. Icons should use 1em sizing to scale with the button's font size.

<Button iconBefore={<PlusIcon />}>Add item</Button>
<Button iconAfter={<ArrowRightIcon />}>Continue</Button>
<Button iconBefore={<DownloadIcon />} variant="outline">
  Export CSV
</Button>

Loading

The loading prop replaces button content with a spinner while preserving button width. The button is automatically disabled during loading.

<Button loading variant="primary">
  Saving...
</Button>

LinkButton

LinkButton is deprecated. Prefer <Button href="..."> directly — the unified Button API renders as an <a> whenever href is set. LinkButton is retained as a thin wrapper for backward compatibility and will be removed in a future major version.

An <a> element styled as a button. Use when the action navigates to a URL. The href prop is required.

// Preferred — unified Button API
<Button href="/docs" variant="outline">
  Read docs
</Button>

<Button
  href="https://github.com/brikdesigns/brik-bds"
  target="_blank"
  rel="noopener"
  iconAfter={<ExternalLinkIcon />}
>
  GitHub
</Button>

// Deprecated — thin wrapper, same result
<LinkButton href="/docs" variant="outline">Read docs</LinkButton>

IconButton

IconButton is deprecated. Prefer <Button icon={...} label="..."> directly — the unified Button API renders icon-only whenever icon is set with no children. IconButton is retained as a thin wrapper for backward compatibility and will be removed in a future major version.

An icon-only button with a required label prop for accessibility. The label becomes the button's aria-label.

// Preferred — unified Button API
<Button icon={<CloseIcon />} label="Close dialog" variant="ghost" />
<Button icon={<TrashIcon />} label="Delete item" variant="negative" />

// Deprecated — thin wrapper, same result
<IconButton icon={<CloseIcon />} label="Close dialog" variant="ghost" />

Don't downgrade emphasis when converting Button → icon-only. A primary action stays a primary action. The 2026-04 IconButton-ghost regression came from agents converting <Button variant="primary"> into <IconButton variant="ghost">. Match the emphasis level, always.

When not to use

  • Don't use ghost for the only action on a page. Ghost is the lowest emphasis tier — it should appear alongside a higher-emphasis sibling.
  • Don't stack two primary buttons next to each other. Only one primary action per surface.
  • Don't use a plain Button for navigation. If clicking takes the user to a new URL, pass href so Button renders as an <a> and right-click + keyboard navigation work correctly.

Accessibility

  • Renders a real <button> element. Keyboard navigation, focus ring, and Enter / Space activation come from the platform.
  • Accessible name comes from the visible label. For IconButton, label becomes aria-label.
  • Disabled state uses the disabled attribute, which removes it from the tab order. For "blocked but explainable" actions, prefer keeping it focusable and showing a tooltip.
  • Loading state announces via aria-busy and disables interaction without removing focus.

API

The full prop reference (with auto-extracted TypeScript types and live controls) lives in Storybook → Components/Action/Button. Summary below.

Button

PropTypeDefault
variantButtonVariant'primary'
sizeButtonSize'md'
iconBeforeReactNode
iconAfterReactNode
loadingbooleanfalse
selectedbooleanfalse
fullWidthbooleanfalse
disabledbooleanfalse

LinkButton

PropTypeDefault
hrefstring (required)
variantButtonVariant'primary'
sizeButtonSize'md'
iconBeforeReactNode
iconAfterReactNode
fullWidthbooleanfalse
childrenReactNode
selectedboolean

IconButton

PropTypeDefault
iconReactNode (required)
labelstring (required)
variantButtonVariant'ghost'
sizeButtonSize'md'
loadingbooleanfalse
selectedbooleanfalse

Type unions

type ButtonVariant =
  | 'primary' | 'outline' | 'secondary' | 'ghost'
  | 'inverse' | 'on-color'
  | 'danger' | 'danger-outline' | 'danger-ghost' | 'destructive'
  | 'positive';

type ButtonSize = 'tiny' | 'sm' | 'md' | 'lg' | 'xl';

CSS Override API

Component-scoped CSS variables exposed on .bds-button. Set these on a wrapping element or directly on the button to override without touching BDS internals.

VariableDefaultControls
--button-active-translate-y1pxDownward shift on :active press
--button-focus-outline-width2pxFocus ring stroke width
--button-focus-outline-offset2pxGap between button edge and focus ring
/* Example: flatten press effect in a toolbar context */
.my-toolbar {
  --button-active-translate-y: 0;
  --button-focus-outline-width: 3px;
}

On this page

💬