Skip to content

Button

A token-driven button with four variants, three sizes, and loading/disabled states.

Preview

Variants

Sizes

States

Default
Hover
Active
Focus
Disabled
Loading

With icons

Full width

Variants

  • Primary -- solid purple background. Use for the main action on a page.
  • Secondary -- transparent with a visible border. Use for supporting actions alongside a primary button.
  • Ghost -- no background or border. Use for low-emphasis actions, toolbars, or inline triggers.
  • Danger -- solid red background. Use for destructive actions like delete or remove.

Sizes

SizeHeightHorizontal paddingFont size
sm32 px12 px13 px
md40 px20 px14 px
lg48 px24 px16 px

States

StateAppearance
DefaultResting state with stable colour fill
HoverBackground shifts one step darker (primary, danger) or shows subtle fill (secondary, ghost)
ActiveDarker than hover, slight inset feel
Focus2 px brand-purple ring with 2 px offset, visible on both light and dark surfaces
Disabled50 % opacity, cursor: not-allowed, no hover effect. Label remains legible.
LoadingSpinner replaces text, button non-interactive, aria-busy="true"

Props

PropTypeDefaultDescription
variant'primary' | 'secondary' | 'ghost' | 'danger''primary'Visual style
size'sm' | 'md' | 'lg''md'Button height and padding
disabledbooleanfalseDisables interaction
loadingbooleanfalseShows spinner, sets aria-busy
fullWidthbooleanfalseStretches to 100% of container
iconLeadingReactNode--Icon before the label
iconTrailingReactNode--Icon after the label
classNamestring--Additional CSS classes
childrenReact.ReactNode--Button label content

All standard <button> HTML attributes are also forwarded.

Code example

React

tsx
import { Button } from '@thepace/lexicon/components';

function Actions() {
  return (
    <div style={{ display: 'flex', gap: 'var(--space-3)' }}>
      <Button variant="primary">Save changes</Button>
      <Button variant="secondary">Cancel</Button>
      <Button variant="ghost" size="sm">Reset</Button>
      <Button variant="danger" disabled>Delete</Button>
      <Button variant="primary" loading>Saving...</Button>
      <Button variant="primary" iconLeading={<PlusIcon />}>Add item</Button>
    </div>
  );
}

Vanilla HTML

html
<button class="lex-button lex-button--primary lex-button--md">
  Save changes
</button>

<button class="lex-button lex-button--secondary lex-button--md">
  Cancel
</button>

<button class="lex-button lex-button--primary lex-button--md lex-button--loading"
        aria-busy="true">
  Saving...
</button>

<button class="lex-button lex-button--primary lex-button--md lex-button--full-width">
  Full width
</button>

CSS class reference

ClassPurpose
.lex-buttonBase styles
.lex-button--primaryPrimary variant
.lex-button--secondarySecondary variant
.lex-button--ghostGhost variant
.lex-button--dangerDanger variant
.lex-button--smSmall size
.lex-button--mdMedium size (default)
.lex-button--lgLarge size
.lex-button--full-width100% width
.lex-button--loadingLoading state with spinner
.lex-button--icon-onlySquare icon-only button

Accessibility

  • Renders a native <button> element with correct type="button" default.
  • Focus ring uses --border-focus (purple-500) with a 2 px offset for clear visibility on both light and dark surfaces.
  • When disabled, the button receives aria-disabled="true" and suppresses click events. Label text remains legible at reduced opacity.
  • When loading, aria-busy="true" is set and the label remains in the DOM for screen readers.
  • Colour contrast between text and background meets WCAG AA (4.5:1) for all variants in both themes.
  • Icon-only buttons require an aria-label for screen reader context.
  • Keyboard: Enter and Space trigger the button action.

Guidelines

Do

  • Use a single primary button per section to establish a clear visual hierarchy.
  • Pair primary with secondary or ghost for multi-action layouts.
  • Add aria-label to icon-only buttons.

Don't

  • Don't use danger variant for non-destructive actions.
  • Don't disable buttons without explaining why (use a tooltip or helper text).
  • Don't nest interactive elements inside a button.

Released under the MIT License. A product by the pace.