FloatButton
Cross-platform Floating Action Button — standalone, grouped (speed-dial), or back-to-top — with safe-area-aware positioning, badge, and full a11y.
At a glance
- Three sub-components:
<FloatButton>standalone,<FloatButton.Group>for click/long-press expansion, and<FloatButton.BackToTop>preset. - Three shapes:
circle | square | extended(extended = pill with inline label) at three sizes (sm40,md56,lg72). - Four variants:
primary | secondary | tertiary | surfacemapped to our semantic tokens. - Cross-platform positioning:
position: fixedon web,position: absoluteon native — auto-respects the bottom safe area inset on iOS/Android viauseSafeAreaInsets()(opt out withrespectSafeArea={false}). - Badge slot —
badge={{ count, dot, tone }}forwards to our<Badge>and pins to the top-right of the button. - Group with click or long-press trigger, controlled or uncontrolled (
open/defaultOpen/onOpenChange), optionalbackdropscrim that closes on tap. actionsarray OR children — the same dual API as Pagination / Breadcrumb. Agents reach for the array; humans reach for JSX.onPressis the primary handler.onClickis also accepted for web-y mental models — if both are defined,onPresswins (and a dev warning fires).hrefintegration on web (renders an<a>). Optionalpositioning='absolute'lets you contain the FAB inside a positioned ancestor (used by these docs previews).- A11y:
role="button"+aria-haspopup="menu",aria-expanded,aria-busy; requiredaccessibilityLabelfor icon-only buttons (dev warning).
Preview
Quick props reference
| Prop | Values |
|---|---|
shape | circle (default), square, extended (auto-pill with label) |
size | sm (40 px), md (56 px, default), lg (72 px) |
variant | primary, secondary, tertiary, surface |
tooltip | String shown next to the trigger on hover (web) / long-press (native) |
dir | ltr (default) / rtl — flips placement left/right |
visible | Controls render — when false the FAB exits with a fade |
onLongPress | Long-press handler — fires after the configured hold |
Shapes
circle (default), square (rounded square — AntD parity), extended (capsule with inline label — auto-applies when both icon and label are present).
Variants
Each variant maps to semantic interactive tokens. surface is the canonical "less attention-grabbing" variant — used by BackToTop.
Badge
badge={{ count }} for numeric, badge={{ dot: true }} for a dot indicator. Tone defaults to 'danger' (the canonical "unread" red); pass badge={{ tone: 'primary', count: 3 }} to recolor.
Group — vertical stack
<FloatButton.Group> clusters action items behind a single trigger. Click the trigger to reveal; click the trigger again, an action, or the backdrop (when enabled) to dismiss.
With backdrop
backdrop renders a soft 32%-black scrim that closes the group on tap — useful on touch devices to give a clear "outside tap to close" affordance.
BackToTop
Preset that listens to window.scrollY (web) or accepts a scrollRef (native), fades in past visibilityThreshold (default 400px), and smooth-scrolls back to the top on press. Defaults to the surface variant.
Positioning
positioning="fixed" (default) pins to the viewport — the canonical FAB behavior. positioning="absolute" pins to the nearest positioned ancestor — used by all the previews on this page so the FAB stays inside the demo card. On native, position: 'fixed' doesn't exist, so we always use 'absolute' rooted at the trigger's parent (the safe-area inset is added automatically).
| Prop | Type | Default | Notes |
|---|---|---|---|
placement | 'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'bottom-right' | RTL flips left/right. |
offset | { x?: number; y?: number } | { x: 24, y: 24 } web, { x: 16, y: 16 } native | Px from the placement corner. |
respectSafeArea | boolean | true | Native-only — auto-adds useSafeAreaInsets().bottom. |
positioning | 'fixed' | 'absolute' | 'fixed' | Use 'absolute' to contain inside a positioned parent. |
Accessibility
- The button uses native
role="button"(web) /accessibilityRole="button"(native). accessibilityLabelis required for icon-only buttons — a dev-only warning fires if missing.aria-busy="true"whileloading;aria-disabled="true"whiledisabled.<FloatButton.Group>addsaria-haspopup="menu"+aria-expandedto the trigger.- The backdrop is a
<Pressable>with the localized "Close" label. - Touch target ≥ 40×40 (sm), 56×56 (md), 72×72 (lg) — all above WCAG 2.2 AA target-size minimum (24×24 / recommended 44×44).
Press handlers — onPress vs onClick
We're React-Native-first, so onPress is the primary handler and works on both platforms. onClick is also accepted for the web-y developer mental model. If both are defined, onPress wins — and a dev-only console warning fires so the intent is unambiguous.
i18n
| Key | Default |
|---|---|
floatButton.backToTop | "Back to top" |
Props
| Prop | Type | Default | Description |
|---|---|---|---|
accessibilityLabel | string | — | A11y label — falls back to `label`; required when icon-only. |
aria-expanded | boolean | — | — |
aria-haspopup | boolean | "menu" | "true" | — | Forward ARIA attributes (used internally by Group for `aria-haspopup`/`aria-expanded`). |
badge | FloatButtonBadge | — | Badge — count, dot, or tone passthrough. |
children | ReactNode | — | Set internally when the button lives inside a `<FloatButton.Group>`. |
className | string | — | — |
dir | enum | — | RTL flips left/right placements. @defaultValue 'ltr' |
disabled | boolean | — | Disable interaction. |
href | string | — | Anchor link — renders as an `<a>` on web. |
icon | ReactNode | — | Required unless `label` is set. |
label | string | — | Inline label — turns the button extended (auto-sets `shape='extended'`). |
loading | boolean | — | Show a spinner in place of the icon. |
offset | { x?: number; y?: number; } | — | Additional offset from the placement corner (px). @defaultValue { x: 24, y: 24 } web; { x: 16, y: 16 } native |
onClick | (event: { preventDefault?: () => void; }) => void | — | Web-y alias for `onPress` — same handler, mapped at the call site. |
onLongPress | (event: GestureResponderEvent) => void | — | Long-press handler — native first-class, web maps to contextmenu. |
onPress | (event: GestureResponderEvent) => void | — | Press handler. We're React-Native-first, so `onPress` is the primary surface and works on both platforms. `onClick` is also accepted for web-mental-model consumers; if BOTH are defined, `onPress` wins (and a dev-only warning fires). |
placement | enum | — | Where to anchor the FAB on the screen. @defaultValue 'bottom-right' |
positioning | enum | — | Positioning strategy. `'fixed'` pins to the viewport (web `position: fixed`, native absolute-on-root); `'absolute'` pins to the nearest positioned ancestor — useful for docs previews and any contained-canvas layout. @defaultValue 'fixed' |
respectSafeArea | boolean | — | On native, auto-add the device's bottom safe-area inset to the offset. **Default is `false`** because RN has no `position: 'fixed'` — every `FloatButton` is parent-relative, and adding the screen-bottom inset to a card-relative offset pushes the button up by ~34pt for no reason. Set this to `true` only when you're certain the FAB's parent reaches the screen edge (e.g. a root-level container without `SafeAreaView`). @defaultValue false |
shape | enum | — | Visual shape. @defaultValue 'circle' (or 'extended' when both icon + label are set) |
size | enum | — | Size — sm=40, md=56, lg=72. @defaultValue 'medium' |
testID | string | — | — |
tooltip | string | — | Tooltip — web: hover/focus tooltip; native (in a group): always-visible label chip. |
variant | enum | surface | Color/tone variant. @defaultValue 'primary' |
visible | boolean | — | Animate in/out. @defaultValue true |