Button for an action and supply its visible label as children. Use IconButton for icon-only controls, with an accessible name from aria-label or aria-labelledby.
For recurring design defaults, use MainButton
or LightButton from twenty-ui/components.
Both share Button’s native props, refs, state, and rendering behavior.
Usage
Appearance
Compare variants, semantic colors, sizes, and common states separately. Each group changes one choice while keeping the others fixed.solid, outline, soft, ghost, or link independently of the neutral, accent, danger, or success color. Sizes are sm and md; the defaults are outline, neutral, and md. fullWidth fills the available width. startIcon and endIcon accept elements and are decorative. elevated adds a shadow and backdrop blur. Neutral outline buttons also gain a bordered surface with matching text and interaction colors. Other variants and semantic colors keep their existing palette. It defaults to false and does not change size or position. shortcut displays keyboard hints on larger screens; bind the shortcuts in your application.
link uses compact text with no border or filled background. It follows its content height at either size, with a smaller font and narrower padding. Its neutral color uses stronger text on hover. The appearance does not determine semantics: use onClick for actions and href for navigation.
disabled and loading prevent activation. Loading keeps the label accessible and communicates a busy state. By default, loadingPosition="center" hides the content while preserving its layout space and centers the spinner. Set loadingPosition to start or end to keep children and the opposite icon visible while replacing the corresponding icon with the spinner.
Your application owns progress formatting and the translated label. Pass the complete status phrase as children, for example Installing (42%).
State ownership
Button triggers an action and does not manage a selected value, so it has no controlled or uncontrolled selection mode. Its native value prop supplies form data, not selection state.
Your application owns loading and disabled, which default to false. Clicking a button does not change these props or automatically show loading while an asynchronous handler runs. Pass the current application state explicitly.
MainButton and LightButton use the same state model.
Popup triggers
Compose a popup trigger withButton, LightButton, or another Button wrapper through render. The trigger supplies data-popup-open, so the button keeps its hover background while the popup is open and uses its active background on hover or press. Each variant and color keeps its own palette. Link backgrounds remain transparent, and disabled or loading buttons keep their disabled appearance. Buttons with aria-pressed="true" retain their existing toggle styling. Popup state does not set aria-pressed or represent a selected value.
Upcoming features
Your application owns feature availability and its label. Compose a disabled button withBadge as children so the label remains part of the button’s accessible name. Supply localized text from your application.
MainButton and LightButton. It replaces the removed soon and soonLabel props.
Animated icons
ComposeButton with AnimatedIconCrossfade to change its icon when application state changes. Pass the animated element to startIcon and the label as children.
IconButton or LightIconButton and animate its icon content. When a disclosure header owns expansion, keep its chevron decorative and expose aria-expanded on the header control. Keep animation styles with the feature and respect prefers-reduced-motion.
Links and forms
render, href renders an anchor with link semantics, unless you explicitly set nativeButton to true. Native anchor attributes such as download, hrefLang, ping, and referrerPolicy reach the anchor. Props shared with native buttons retain their Base UI types. To supply an anchor MIME type, put it on the renderer, for example render={<a href="/records.csv" type="text/csv">Download records</a>} with nativeButton={false} and role="link". Disabled and loading links cannot activate.
Use render to compose another element or a router link. nativeButton describes the rendered element and defaults to true for custom renderers. Set nativeButton={false} and role="link" for a navigation anchor. The anchor or router component can own its destination without a Button href.
<button>, keep nativeButton true. For a non-native action element such as a <div>, use nativeButton={false}; Base UI supplies button keyboard behavior. An explicit nativeButton or role is preserved even when href is also supplied.
Buttons default to type="button". Set type="submit" or type="reset" explicitly for forms. Native attributes, event handlers, event details, and refs reach the rendered root. A ref receives the actual button, anchor, or custom root element. Keyboard handlers retain Base UI event methods, including preventBaseUIHandler() to cancel its built-in keyboard action. Base UI’s render callback receives both root props and the current disabled state; spread the props onto your root to retain composition behavior.
Group defaults
ButtonGroup supplies a shared variant, color, and size. An explicit prop on a child takes precedence over the group default. When neither sets a prop, the component default applies.Migration from ClickToActionLink
ImportButton from twenty-ui/primitives/input and replace ClickToActionLink with Button variant="link". Keep href, target, and rel for navigation. Actions without href now render a native button with keyboard activation. Native attributes, events, and refs pass through to the root.
Props
string | ((state: ButtonState) => string | undefined)
CSS class applied to the element, or a function that
returns a class based on the component’s state.
"accent" | "danger" | "neutral" | "success"
default:"neutral"
Semantic color of the button.
boolean
default:"false"
Adds a shadow and backdrop blur. Neutral outline buttons also use elevated surface colors.
ReactNode
Decorative content displayed after the label.
boolean
default:"false"
Whether the button should be focusable when disabled.
boolean
default:"false"
Expands the button to fill its container width.
string
Destination URL. Renders a native anchor when
render is omitted unless nativeButton is explicitly true. Custom renderers retain their own element contract.boolean
default:"false"
Shows a loading indicator and disables activation.
"center" | "end" | "start"
default:"center"
Where the loading indicator appears.
center hides the content while preserving its layout space; start and end keep children and the opposite icon visible and replace the corresponding icon with the spinner.boolean
default:"true (false for the built-in href anchor)"
Whether the rendered root is a native button. Defaults to true for custom renderers. The built-in
href anchor defaults to false; an explicit value takes precedence.ReactElement<unknown, string | JSXElementConstructor<any>> | ComponentRenderFn<HTMLProps, ButtonState>
Caller-supplied root element or renderer. Native attributes, handlers, event details, and refs reach the rendered root. Use
nativeButton={false} and role="link" for a custom navigation anchor. Router integration belongs to the caller.AriaRole
Role of the root element. Use
link for custom navigation renderers.ShortcutDefinition
Flat key array for simultaneous keys, or nested key arrays for an ordered sequence. Displayed on non-mobile screens.
string
Localized text between sequence steps. Defaults to then.
"md" | "sm"
default:"md"
Button height:
sm (24px) or md (32px). The link variant uses its content height at either size.ReactNode
Decorative content displayed before the label.
CSSProperties | ((state: ButtonState) => CSSProperties | undefined)
Style applied to the element, or a function that
returns a style object based on the component’s state.
"ghost" | "link" | "outline" | "soft" | "solid"
default:"outline"
Visual treatment of the button surface.
link uses compact text styling without a filled surface or border; render composition determines native semantics.shortcut={['Mod', 'Enter']} for simultaneous keys or shortcut={[['G'], ['P']]} for successive presses. See Shortcut for nested combinations and the shared string formatter.