Button
Atom
Six intents × three sizes (plus the icon square) × disabled / focus / hover states. Always semantic: the intent communicates what the button does (default = the primary action, destructive = irreversible, ghost = chrome).
Playground
Every control sets a real prop of ButtonProps. The JSX below updates as you go.
Web
iOS
Android
JSX · ButtonProps
Variants
20 examples from src/atoms/button/button.md, each rendered live from its own JSX.
Sizes
States
Hover, focus and press are pinned so they can be compared side by side; the rest are real props.
Anatomy
1
Container
2
Label
3
Leading icon
4
Trailing icon
5
Count badge
Dimmed parts appear only in some states or variants.
Props
62 props on ButtonProps, grouped as they are declared in source. Required props are marked *.
MeasureProps · width steps xxxs 192 · xxs 256 · xs 320 · sm 384 · md 448 · lg 512 · xl 576 · xxl 672 · xxxl 768 · wide 896 · wider 1024 · widest 1152 · page 1280, plus start to pin to the leading edge.
Accessibility and keyboard
Keyboard
Enter
Activates
Space
Activates
Tab
Moves focus on
Semantics
Roles
button
link
States
busy
disabled
selected
ARIA on web
aria-busy
aria-disabled
aria-expanded
aria-haspopup
aria-hidden
Below the platform minimum, the touch area grows to 44 pt on iOS and 48 dp on Android without moving the layout.
From the docs
Six intents × three sizes (plus the icon square) × disabled / focus / hover states. Always semantic: the intent communicates what the button does (default = the primary action, destructive = irreversible, ghost = chrome).
On the web a button is 32, 40 or 48 tall (small, default, large) with the 10 px control corner, a 500-weight label at 13, 14 or 16 px, and an 8 px gap beside its icons. The call to action fills with the accent, destructive with the status red, both under a lit top edge; secondary is the quiet well with a boundary, outline the boundary alone, ghost and link bare (the link underlined). A filled button brightens and rises 1 px under the pointer over 150 ms and settles back when it leaves; the other looks
Pass ref to access the interactive Pressable, including link buttons. Use useRef<ComponentRef<typeof Button>>(null) from React, or useRef<View>(null) with React Native's View type. Object and callback refs are supported and detach on unmount. Calling ref.current?.focus() or .blur() delegates to the host without activating the control. Browser focus is supported; native focus depends on the platform and React Native version, and is separate from accessibility focus.
Utility icon actions use icon with compactIcon (28), mediumIcon (32), or largeIcon (34). wideIcon gives the compact box two extra pixels of horizontal room; circle rounds it fully. Pair with ghost for a plain surface, outline for a boundary, or ghost inset for a quiet fill. steadyPress keeps the resting look while pressed, and count carries a notification badge over the upper trailing corner. The button still owns its accessible name, hover wash, and native touch-target extension.
scopeTrigger ghost steadyPress is a bare 32 px menu trigger with a 600-weight 14px label; filterTrigger ghost steadyPress the same with a muted 13px label. Both take the well's wash on hover. Supply the trailing chevron through iconRight, and announce the menu with expanded and haspopup.
A button that NAVIGATES should be a real link, not a press handler that sets location. Pass href and the web render becomes a genuine browser link: middle-click, cmd-click, and open-in-new-tab all work, crawlers see the destination, and assistive tech hears a link. Native platforms have no anchors, so pair href with an onPress that runs the same navigation through your router; while disabled or loading the anchor is suppressed exactly like onPress. hrefAttrs carries the anchor attributes react-nativ
Platform skins
The playground's props on each platform: same tree, native shapes, type and touch sizes.
Usage
A button's whole job is to fire onPress. Wire it to your own handler and every press runs it.
On the web a button is 32, 40 or 48 tall (small, default, large) with the 10 px control corner, a 500-weight label at 13, 14 or 16 px, and an 8 px gap beside its icons. The call to action fills with the accent, destructive with the status red, both under a lit top edge; secondary is the quiet well with a boundary, outline the boundary alone, ghost and link bare (the link underlined). A filled button brightens and rises 1 px under the pointer over 150 ms and settles back when it leaves; the other looks take the well's wash at once. A press scales the button to 96% and darkens it, unless steadyPress. A disabled button fades to 40%; a loading one keeps its full look while its spinner runs. A disabled or loading button stays put, and Reduce Motion makes the lift instant. On iOS the button is the 50 pt capsule with a semibold 17 pt label, on Android the 40 dp Material 3 stadium, both in the same colours.
Pass ref to access the interactive Pressable, including link buttons. Use useRef<ComponentRef<typeof Button>>(null) from React, or useRef<View>(null) with React Native's View type. Object and callback refs are supported and detach on unmount. Calling ref.current?.focus() or .blur() delegates to the host without activating the control. Browser focus is supported; native focus depends on the platform and React Native version, and is separate from accessibility focus.
softHover dims a link to 85% on hover in place of the wash. Inverse actions use a translucent white hover wash over a branded surface.
Real links (href)
A button that NAVIGATES should be a real link, not a press handler that sets location. Pass href and the web render becomes a genuine browser link: middle-click, cmd-click, and open-in-new-tab all work, crawlers see the destination, and assistive tech hears a link. Native platforms have no anchors, so pair href with an onPress that runs the same navigation through your router; while disabled or loading the anchor is suppressed exactly like onPress. hrefAttrs carries the anchor attributes react-native-web forwards (target, rel, download).
Workflow actions
Use outline quiet for a muted secondary operation and outline inverse on a dark branded surface. consistent gives short workflow actions a shared minimum width; comfortable supplies the roomier surround used for a compact featured action.
Touch area
On iOS and Android a Button smaller than the platform's minimum touch target (44pt, 48dp) keeps its size and extends only its touch area to the minimum, from its first frame, so nothing moves. React Native bounds that area by two rules of its own. It never reaches past a native container that does not contain it: a container of yours that clips (overflow hidden, a scroll view's edge) stops it at that edge. A container that paints, carries a testID or handles pointer events admits it, because such a container records the touch area at its layout and the Button has it from its first one. Every kit control does the same for itself; a control the kit lacks belongs in the kit, never in a Pressable of your own (see DESIGN.md, Shapes). And where two touch areas overlap, the later sibling takes the tap: on Android two small icon Buttons 8 apart in a Row both reach across the gap between them, so a tap anywhere in it presses the second. Leave at least twice the extra touch area between small Buttons you place side by side (16 between two small icon Buttons on Android, 8 on iOS).
Tokens
Read from the playground render. Values follow the scheme and the ion hue.
--bls
--bw
--fd
--ft
--ion
--ion-fg