Skip to content

Design system

Documentation

DesignComponents / Button

Components / Button

Stable

Button

Use Button for an action that changes state or submits intent. Use a real link when the user is navigating somewhere.

On this page

Examples

Basic action

A focused primary action with a verb-led label.

Basic Button action

Intent and emphasis

Compare the seven public variants without inventing a new visual state.

Button variant comparison

Color palettes

Choose a deliberate hue while keeping Button's existing intent and emphasis variants.

Button color palette comparison

Text and icon sizes

Use the shared control scale for text actions and icon-only targets.

Button text and icon size comparison

Icon before or after

Decorative icons support a visible label and remain hidden from assistive technology.

Buttons with leading and trailing icons

Unavailable action

Disabled actions stay visible and explain an unexpected constraint nearby.

A workspace owner must approve this action.

Disabled Button with supporting explanation

Loading action

Loading prevents repeat activation while preserving the button footprint.

Loading Button action

Destructive action with confirmation

Irreversible deletion uses the shared Dialog confirmation pattern.

Destructive Button with Dialog confirmation

Button playground

Change a prop and the live preview and copied example stay in sync.

Use Tab, Enter, and Space to test the real control.

Button preview with the selected props

When to use

Use Button for an in-place action: saving, submitting, opening a menu, or changing a local state. Use an anchor when the primary outcome is navigation, even if the link is styled like a button.

Use Button

“Save changes”, “Add teammate”, and “Try again” all change application state.

Use a link instead

“View report” and “Open settings” take the person to another URL or route.

Usage

The copied playground code is the smallest runnable form for the selected props. A static composition with several actions looks like this:

button-basic.tsx
import { Button } from "@phuctech/ui/components/button";

export function Example() {
  return <Button>Save changes</Button>;
}
  • Keep the label verb-led and specific. Do not make an icon carry the only meaning.
  • Use disabled when the action cannot be performed; explain a non-obvious reason nearby.
  • Use loading for an in-flight action. It disables repeat activation, keeps the button size, exposes aria-busy, and uses the unavailable cursor.
  • Available buttons use cursor-pointer; disabled and loading buttons use cursor-not-allowed.
  • Confirm an irreversible destructive action in a shared dialog before calling the action.

Variants and states

Button variants, sizes, and states
OptionUse for
defaultThe primary action in a group.
outlineA transparent secondary action with a visible boundary.
dashedA lower-emphasis action when the boundary should feel open or optional.
secondarySupporting actions on a quiet surface.
ghostLow-emphasis actions in navigation or toolbars.
destructiveActions that remove or invalidate something.
colorPaletteExplicit hue for non-destructive variants while preserving their intent and emphasis.
linkA button-shaped text action; use an anchor for navigation.
xs, sm, md, lgMatch the control to its surrounding density.
icon-*Icon-only controls; always provide an accessible name.
disabledUnavailable action; it remains visible but cannot be activated.
loadingIn-flight action; the control is disabled and shows a stable-size spinner.

Icons

Use PhucTech icons from @phuctech/ui/icons. An icon next to a text label is decorative and should have aria-hidden="true". An icon-only Button needs a concise aria-label and one of the icon-* sizes.

API

Button forwards Base UI button props and adds the visual variants below. Composition uses Base UI's render prop; this API does not add an asChild alias.

Component API
PropTypeDefaultDescriptionConstraints
variant"default" | "outline" | "dashed" | "secondary" | "ghost" | "destructive" | "link""default"Visual emphasis and intent.Use one primary action per group.
size"md" | "xs" | "sm" | "lg" | "icon-md" | "icon-xs" | "icon-sm" | "icon-lg""md"Control height, padding, and icon scale.Use an icon size only for icon-only content.
loadingbooleanfalseShows a spinner, sets aria-busy, and disables the button.Use for an in-flight action; do not use as a permanent disabled state.
loadingLabelstring"Loading"Accessible status text while loading.Use a specific phrase such as “Saving changes” when useful.
colorPalette"neutral" | "red" | "orange" | "amber" | "yellow" | "lime" | "green" | "teal" | "cyan" | "blue" | "indigo" | "violet" | "pink"Hue for non-destructive Button variants.The destructive variant remains danger red.
disabledbooleanfalsePrevents interaction and communicates unavailable state.Explain a non-obvious reason in nearby text.
renderReactElement | ComponentRenderFnnative buttonBase UI composition hook for rendering another element or component.Use render; there is no asChild prop.
aria-labelstringAccessible name for icon-only controls.Required when the visible content has no text name.

All native button props, event handlers, children, and className are forwarded. The underlying element remains a native button unless render composes it.

Tokens

Component tokens
TokenRoleEffective valueUsage
--size-control-*Control heightxs / sm / md / lgMaps each size to the shared control scale.
--radius-*Boundary geometrysm / md / lgKeeps button corners aligned with fields and surfaces.
--focus-ring-*Keyboard focus3px / 2px / semantic focusShared visible ring for keyboard users.
--motion-duration-*Interaction timingfast / normalTransitions use the motion scale and respect reduced motion.
--primary, --secondary, --destructiveIntent colorssemantic aliasesVariant colors resolve through the token layer, including dark mode.

Accessibility

  • Native keyboard activation works with Enter and Space; focus uses the shared visible ring.
  • Text labels are preferred. Icon-only buttons must provide aria-label.
  • loading disables repeated activation and exposes a busy state; keep surrounding feedback for long operations.
  • Do not use color alone to communicate destructive or disabled intent.

Do and don’t

Do

Use one clear primary action, keep labels specific, and test focus, disabled, and loading states with a keyboard.

Don’t

Use a Button for navigation, nest interactive elements, or hide the only label inside a decorative icon.

  • Sizing defines control heights and touch targets.
  • Accessibility documents focus and contrast contracts.
  • DropdownMenu composes Button as its trigger with Base UI's render prop.