Skip to main content

Choose an action style, name icon controls, and handle loading or unavailable actions without losing context.

4 min read

Use Button for an action on the current screen. Give it a specific verb, such as Save changes or Export, and choose its appearance according to the action's importance and consequence. For navigation, use a link with button styling.

tsx
import { Button, LinkButton } from '@tale/ui/button';

Variants

Live example
VariantAppropriate use
primary (default)The main action in the current task.
secondaryA supporting action such as Cancel.
ghostA quiet action in a toolbar or row.
destructiveA consequential removal, such as deleting or revoking.
warningA consequential action that requires caution.
successA positive action or completion treatment where the meaning is clear.
linkA text-style action; this remains a button unless you change its element.

Use one primary action per decision area. A destructive color does not ask for confirmation or enforce permissions; the host owns those behaviors. Use a confirmation dialog when the decision needs explanation.

Sizes and responsive labels

Live example

default is 36px high (h-9), and sm is 32px (h-8). icon and icon-sm are square controls at those heights. There is no lg size. The text-style link variant uses an automatic height instead of the fixed control box.

Live example

Pass a Lucide component through icon; Button supplies a 16px decorative icon and spacing. collapseLabel visually hides the text below sm while preserving the accessible name. Pair it with an icon so the mobile control still has visible content.

Show work in progress

Live example

Choose Save changes to see a short simulated operation. isLoading displays a spinner, sets aria-busy, and disables activation. The label remains in place. The example resets after 1.5 seconds; a real screen should clear loading when its request settles.

Set type="submit" for form submission and type="button" for other form actions. The component does not generally override the browser's default button type. Disable duplicate submissions in your handler as well, and keep a failed save visible near the form.

Explain an unavailable action

Live example

Focus Publish with the keyboard to read why it is unavailable. When disabled and a nonempty disabledReason are present, the component uses aria-disabled instead of native disabled, preserves keyboard focus, and blocks clicks, Enter, and Space. A plain disabled button leaves the tab order.

The reason only applies while disabled is true. Describe what would make the action available; use visible nearby text when the explanation is essential to completing the task. A disabled UI control is not an authorization check.

Live example

asChild merges styling onto one child element, such as an anchor. Keep link behavior on that child. Tooltips and disabledReason are suppressed in this mode, and an anchor does not acquire native button disabling. Do not use disabled as a way to prevent a link from navigating.

For TanStack Router destinations, LinkButton accepts href, params, search, and prefetch. It requires router context. For an external URL, an anchor inside Button asChild keeps normal browser link behavior.

Props

Native button attributes pass through. These are the component-specific choices:

PropValues or defaultBehavior
variantprimary, secondary, ghost, destructive, warning, success, link; default primaryVisual emphasis.
sizedefault, sm, icon, icon-sm; default defaultControl dimensions.
icon, iconClassNameLucide component; optional extra classesLeading decorative icon.
isLoadingfalseSpinner, busy state, and activation blocking.
disabledReasonOptional React contentFocusable explanation while disabled; unavailable with asChild.
fullWidthfalseFills the available width.
collapseLabelfalseHides text visually below sm.
asChildfalseStyles one child instead of rendering a button.
titleOptional stringTooltip; also names an icon-sized Button unless aria-label overrides it.
tooltipOptional React contentOverrides the visible tooltip text.
tooltipSidetop, right, bottom, left; default topTooltip placement.
tooltipOpen, onTooltipOpenChangeOptional controlled stateLets a caller control a state-announcing tooltip.

Accessibility and alternatives

Icon-sized Buttons require aria-label or title at the type level. Text-sized buttons still need meaningful children; the type system cannot judge the label's quality. A tooltip alone is a description, not the control's name. title on a text Button adds a tooltip without replacing its visible accessible name.

Use IconButton from @tale/ui/icon-button for a toolbar glyph: it requires aria-label, defaults to ghost, and supplies a tooltip. For a table's create action, prefer DataTable.addAction; for a row menu, use the table's column builders. Check keyboard focus, the disabled explanation, and the loading state in both themes.