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
| Size | Height | Horizontal padding | Font size |
|---|---|---|---|
sm | 32 px | 12 px | 13 px |
md | 40 px | 20 px | 14 px |
lg | 48 px | 24 px | 16 px |
States
| State | Appearance |
|---|---|
| Default | Resting state with stable colour fill |
| Hover | Background shifts one step darker (primary, danger) or shows subtle fill (secondary, ghost) |
| Active | Darker than hover, slight inset feel |
| Focus | 2 px brand-purple ring with 2 px offset, visible on both light and dark surfaces |
| Disabled | 50 % opacity, cursor: not-allowed, no hover effect. Label remains legible. |
| Loading | Spinner replaces text, button non-interactive, aria-busy="true" |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
variant | 'primary' | 'secondary' | 'ghost' | 'danger' | 'primary' | Visual style |
size | 'sm' | 'md' | 'lg' | 'md' | Button height and padding |
disabled | boolean | false | Disables interaction |
loading | boolean | false | Shows spinner, sets aria-busy |
fullWidth | boolean | false | Stretches to 100% of container |
iconLeading | ReactNode | -- | Icon before the label |
iconTrailing | ReactNode | -- | Icon after the label |
className | string | -- | Additional CSS classes |
children | React.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
| Class | Purpose |
|---|---|
.lex-button | Base styles |
.lex-button--primary | Primary variant |
.lex-button--secondary | Secondary variant |
.lex-button--ghost | Ghost variant |
.lex-button--danger | Danger variant |
.lex-button--sm | Small size |
.lex-button--md | Medium size (default) |
.lex-button--lg | Large size |
.lex-button--full-width | 100% width |
.lex-button--loading | Loading state with spinner |
.lex-button--icon-only | Square icon-only button |
Accessibility
- Renders a native
<button>element with correcttype="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 receivesaria-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-labelfor 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-labelto 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.