Button
Displays a button, or a component that looks and behaves like one.
Usage
import { Button } from '@epilot/spark-ui/button'<Button>Save changes</Button>A bare <Button> follows the active theme: its look comes from the Theme's
styleVariant, accentColor, and highContrast knobs. Set styleVariant
and color to pin a look on a single instance, independent of the ambient
theme.
Guidelines
styleVariant sets visual weight, heaviest to lightest: solid >
surface / soft > outline > ghost. A bare Button inherits its weight
from the theme; set styleVariant to override it, and avoid two solid
buttons side by side.
color picks the scale that paints the button: accent, neutral, or
danger. Use color="danger" for an error or destructive action; it composes
with any style: a solid Delete, or a ghost Remove.
Semantic hierarchy (which action is primary, secondary, or tertiary) is a composition concern owned by whatever places the button (e.g. the portal builder's button block), not a Button prop.
Examples
Theme-driven default
A bare <Button> takes its look from the ambient theme's styleVariant knob,
so the same markup restyles automatically inside a themed region.
Styles & colors
styleVariant and color set together pin a button's look regardless of the
ambient theme.
Danger
color="danger" composes with any style.
High contrast
High contrast is a theme-level knob (<Theme highContrast> / data-high-contrast), not a Button prop. It strengthens any button toward higher-contrast steps automatically. Solid fills deepen; non-solid tiers darken their ink and border.
Sizes
With icons
Icons default to 16px (size-4) inside a button; give the icon its own
size-* class to override.
Icon-only
Use the square icon-small / icon / icon-medium sizes for icon-only
buttons, and always pass an aria-label.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
styleVariant | 'solid' | 'soft' | 'surface' | 'outline' | 'ghost' | — | Visual weight. Pins the button's style, overriding the theme's styleVariant. |
color | 'accent' | 'neutral' | 'danger' | — | Which scale paints the button. Setting color alone pins styleVariant to solid. |
size | 'small' | 'default' | 'medium' | 'icon-small' | 'icon' | 'icon-medium' | 'default' | Button size, including the square icon-only sizes. |
render | ReactElement | — | Replaces the rendered element (e.g. a router <Link/>) instead of the native <button>. |
nativeButton | boolean | true | Set to false alongside render when the target element isn't a native <button>. |
Accessibility
- Reconsider disabling at all. A disabled button hides why an action is unavailable, drops out of the tab order, and gives no feedback when a user clicks it anyway; see Disabled buttons suck. Prefer keeping the button enabled and surfacing the reason on interaction (a validation message, inline error, tooltip) instead of disabling it preemptively. The same reasoning applies to any control with a
disabledstate, not just Button. - If you do disable, prefer
aria-disabledoverdisabled. It keeps the button focusable and announced instead of removing it from the tab order, and gets the same dimmed look (opacity-50) and blocked pointer events asdisabled. But it doesn't block keyboard activation on its own, so guard the handler yourself (e.g.onClick={isValid ? handleSubmit : undefined}). - Icon-only buttons need a label. With no text content, pass
aria-labelto give screen readers something to announce. typedefaults to"button", so a button inside a form won't submit it unless you settype="submit".- Disabled +
render: non-button elements can't bedisabled, so Base UI setsaria-disabledand blocks interaction on non-native elements instead. The element stays in the tab order. Non-button render targets also getrole="button". - The focus ring meets WCAG 1.4.11 (3:1 against adjacent colors) on every accent.