Components / Actions

Button nativelite

A press target with a label and optional icon (or one custom child).

A2UI basic
Button
Children
one
Loading the renderer…
Button rendered by the React renderer on Web
Web React
Button rendered by the SwiftUI renderer on iOS
iOS SwiftUI
Button rendered by the Compose renderer on Android
Android Compose
Button rendered by the gpui renderer on Desktop
Desktop gpui

View exponential-ui-button, painted by each native renderer.

variant · default
variant · secondary
variant · outline
variant · ghost
variant · destructive
variant · link
size · sm
size · default
size · lg
size · icon
disabled
PropTypeDefaultDescription
labelbindstring–The button text (omit for an icon-only button).
iconicon–An icon before the label, or alone with size icon.
variantdefault | secondary | outline | ghost | destructive | link"default"default = the primary action, destructive = dangerous, ghost/link = quiet.
sizesm | default | lg | icon"default"Control height class; icon is a square icon-only button.
disabledbindbooleanfalseShows it inert.
loadingbooleanfalseShows a spinner and refuses presses.
submitbooleanfalseSubmits the enclosing Form on press.
NameKind
on.pressevent: runs an action
Role
button
Semantics
button
KeysAction
EnterSpacepress
–A submit button submits the enclosing Form
Recipe parts
Button/rootButton/iconButton/labelButton/spinner
Keyed by
variantsizedisabledloading:hover:pressed:focus:disabled:checked:open:selected:invalid:dragover

Themes · Write a theme

Primary icon button

A 32px circle of card fill under a card stroke, glyph 16px at 70% foreground; hover fills to active and the glyph goes full strength.

More

The SHAPE is the meaning (EXP-771, narrowed by EXP-862): a circle marks the PRIMARY action and nothing else wears one. That is play / start, send, the rail's New issue and Search, a mobile FAB, and the "+" that adds. Every other icon-only control is the ghost icon button. The remaining exception is a picker TRIGGER, which is a rounded square: see icon picker.

Webok
buttonVariants variant="glass" size="icon-sm"packages/ui/src/button.tsx
Desktopok
controls::glass_icon_buttonapps/desktop/crates/ui/src/controls.rs

Ghost icon button

The SECONDARY icon button (EXP-862): the same 32px box and the same 16px glyph at 70% foreground, with no circle, no fill and no border at rest.

More

Hover is the only paint it carries, the row wash under the MD corner, and the glyph goes full strength; a toggle that is ON says so with aria-pressed and keeps that wash (the editor rail's marks, EXP-960). Everything that is not the primary action wears this one: the "…" overflow, close, the folder and file-list toggles, the chevrons (back, fold, reorder), trash and remove, refresh. Put a circle here and the surface ends up with three things asking to be pressed and no way to tell which one it wants.

Webok
buttonVariants variant="ghost" size="icon-sm"packages/ui/src/button.tsx
Androidok
CircleIconButton(borderless = true)apps/android/app/src/main/java/com/exponential/app/ui/components/CircleIconButton.kt

one composable, two shapes: borderless drops the circle and the stroke and keeps the hover fill

Desktopok
controls::buttons::ghost_icon_buttonapps/desktop/crates/exponential-ui-gpui/src/controls/buttons.rs

Primary submit

Full width, padding 14/16, radius 10, solid primary.

More

Disabled drops to card fill with a card stroke and 50% foreground. EXP-1176: ONE shape on every client — the specimen is the web's own Button, a radius-10 rectangle like the mobile sheet submit and desktop web_md. A text button is never a capsule; the capsule is the pill.

Webok
Button (variant default)packages/ui/src/button.tsx

the same radius-10 rectangle on every client since EXP-1176; the mobile sheet submit is the full-width form of it

Desktopok
controls::pills::glass_pill_button_primaryapps/desktop/crates/exponential-ui-gpui/src/controls/pills.rs

Text button

EXP-962: a control made of WORDS — `size="inline"`, 12px, no box, no height of its own, sitting in the run of muted text around it.

More

Two variants, and the difference is what happens when it is pressed: `text` is muted, brightens under the pointer and never underlines, because it toggles something IN PLACE (a fold's Show more / Show less, "Back to the current step"); `link` takes the primary colour and underlines on hover, because it GOES somewhere (a session band's "Continues in a newer run", a stack band's `↓ #APP-14`). Anything that wants a box is the pill or the primary submit — four call sites hand-drew one of these two shapes before.

Webok
Button variant="text" / variant="link", size="inline"packages/ui/src/button.tsx
iOSleftover
Button("Show more").buttonStyle(.plain)apps/ios/Exponential/UI/Session/AgentSessionView.swift

inlined twice with its own caption2 font and tertiary opacity; no shared text button exists

Androidleftover
ShowMoreToggleapps/android/app/src/main/java/com/exponential/app/ui/session/AgentSessionScreen.kt

a private clickable Text on the session screen; nothing else may reach it

Desktopok
controls::text_button (TextButtonVariant::Text)apps/desktop/crates/ui/src/controls.rs

EXP-963: the output card's and the body's Show more, the edited-files footer ride it