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.

accent
neutral
danger
solid
soft
surface
outline
ghost

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

PropTypeDefaultDescription
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.
renderReactElementReplaces the rendered element (e.g. a router <Link/>) instead of the native <button>.
nativeButtonbooleantrueSet 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 disabled state, not just Button.
  • If you do disable, prefer aria-disabled over disabled. 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 as disabled. 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-label to give screen readers something to announce.
  • type defaults to "button", so a button inside a form won't submit it unless you set type="submit".
  • Disabled + render: non-button elements can't be disabled, so Base UI sets aria-disabled and blocks interaction on non-native elements instead. The element stays in the tab order. Non-button render targets also get role="button".
  • The focus ring meets WCAG 1.4.11 (3:1 against adjacent colors) on every accent.

On this page