UnflowUI
Components

Button

The foundational interactive element. Eight semantic variants covering every action hierarchy from primary CTA to link-style navigation.

Overview

Button is the most-used component in the system. Its eight variants map to distinct interaction hierarchies: primary actions, secondary alternatives, tertiary supporting actions, destructive operations, link-style navigation, and social sign-in.

The loading prop replaces the label with a spinner without changing the button's dimensions, preventing layout shift. When neither label nor aria-label is provided, a console warning fires to catch accessibility gaps early.

Import

import { Button } from '@unflow/ui/components/Button';

Props

PropTypeDefaultDescription
labelstringButton text
variant'primary' | 'secondary' | 'tertiary' | 'link' | 'linkHighlight' | 'destructive' | 'destructiveSecondary' | 'social''primary'Visual and semantic variant
size'xs' | 'sm' | 'md' | 'lg''sm'Button size
loadingbooleanfalseShows a spinner loading state
disabledbooleanfalseDisables the button
fullWidthbooleanfalseStretches the button to fill its container
startIconReactNodeIcon before the label
endIconReactNodeIcon after the label
onClickMouseEventHandlerClick handler

All standard HTMLButtonElement props are forwarded to the underlying <button>.

Variants

<Button label="Primary" variant="primary" />
<Button label="Secondary" variant="secondary" />
<Button label="Tertiary" variant="tertiary" />
<Button label="Destructive" variant="destructive" />
<Button label="Destructive Secondary" variant="destructiveSecondary" />
<Button label="Link" variant="link" />
<Button label="Link Highlight" variant="linkHighlight" />

Primary — Highest-emphasis action. Use for the single most important action on a surface. Background #14141A, hover bg-grey-700.

Secondary — Supporting action. bg-grey-50 with border-grey-300.

Tertiary — Low-emphasis. No background, hover bg-grey-100.

Destructive / Destructive Secondary — Irreversible operations. Full red vs. light red for two levels of emphasis.

Link / Link Highlight — Inline text-link style rendered as <button>. Underline is a CSS ::after pseudo-element at -bottom-0.5 to avoid descender clipping. linkHighlight uses text-indigo-500.

Social — OAuth provider buttons. Same visual as Secondary — pair with a provider logo in startIcon.

Sizes

<Button label="XS" size="xs" />   {/* 28px min-height */}
<Button label="SM" size="sm" />   {/* 32px */}
<Button label="MD" size="md" />   {/* 40px */}
<Button label="LG" size="lg" />   {/* 48px */}

Loading state

<Button label="Save changes" loading />

The button's dimensions are preserved — no layout shift. The loading label is i18n-aware and defaults to the button.loading translation key.

Customize the loading slot:

<Button
  label="Save changes"
  loading
  slotProps={{
    loading: {
      label: { content: 'Saving your work…' },
      disableLabel: false,
      disableSpinner: false,
      spinner: { size: 16, thickness: 2 },
    },
  }}
/>

Disabled

<Button label="Disabled" disabled />

Sets both disabled and aria-disabled on the native <button>. Opacity drops to 30% via disabled:opacity-30.

Full width

<Button label="Full width" fullWidth />

With icons

import { Bell } from '@unflow-ui/icons/communication/bell';

<Button label="Notifications" startIcon={<Bell />} />
<Button label="Continue" endIcon={<ArrowRight />} />

Customization (slotProps)

SlotPropsDescription
loading.label{ content?: string; className?: ClassName }Custom loading text and styling
loading.spinnerPick<ISpinner, 'className' | 'size' | 'speed' | 'thickness'>Spinner config during loading
loading.disableLabelbooleanHide the loading text
loading.disableSpinnerbooleanHide the spinner icon
label{ className?: ClassName }Override label text styles
startIconRootBaseHTMLProps<HTMLDivElement>Override start icon wrapper
endIconRootBaseHTMLProps<HTMLDivElement>Override end icon wrapper

Accessibility

  • Renders a native <button> — keyboard and screen-reader accessible by default.
  • aria-label defaults to the label prop value.
  • Dev warning fires when neither label nor aria-label is provided.
  • disabled sets both HTML disabled and aria-disabled="true".
  • Loading state retains aria-label so screen readers still announce the button's purpose.