nori-ui

FloatButton

Cross-platform Floating Action Button — standalone, grouped (speed-dial), or back-to-top — with safe-area-aware positioning, badge, and full a11y.

7.5 kBgzipped

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 (sm 40, md 56, lg 72).
  • Four variants: primary | secondary | tertiary | surface mapped to our semantic tokens.
  • Cross-platform positioning: position: fixed on web, position: absolute on native — auto-respects the bottom safe area inset on iOS/Android via useSafeAreaInsets() (opt out with respectSafeArea={false}).
  • Badge slotbadge={{ 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), optional backdrop scrim that closes on tap.
  • actions array OR children — the same dual API as Pagination / Breadcrumb. Agents reach for the array; humans reach for JSX.
  • onPress is the primary handler. onClick is also accepted for web-y mental models — if both are defined, onPress wins (and a dev warning fires).
  • href integration on web (renders an <a>). Optional positioning='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; required accessibilityLabel for icon-only buttons (dev warning).

Preview

Direction:
Locale:

Quick props reference

PropValues
shapecircle (default), square, extended (auto-pill with label)
sizesm (40 px), md (56 px, default), lg (72 px)
variantprimary, secondary, tertiary, surface
tooltipString shown next to the trigger on hover (web) / long-press (native)
dirltr (default) / rtl — flips placement left/right
visibleControls render — when false the FAB exits with a fade
onLongPressLong-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).

Direction:
Locale:

Variants

Each variant maps to semantic interactive tokens. surface is the canonical "less attention-grabbing" variant — used by BackToTop.

Direction:
Locale:

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.

Direction:
Locale:

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.

Direction:
Locale:

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.

Direction:
Locale:

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.

Direction:
Locale:

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).

PropTypeDefaultNotes
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 } nativePx from the placement corner.
respectSafeAreabooleantrueNative-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).
  • accessibilityLabel is required for icon-only buttons — a dev-only warning fires if missing.
  • aria-busy="true" while loading; aria-disabled="true" while disabled.
  • <FloatButton.Group> adds aria-haspopup="menu" + aria-expanded to 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.

<FloatButton icon={<Plus />} accessibilityLabel="New" onPress={() => …} />
 
// Both are accepted, but onClick is silently shadowed if onPress is set:
<FloatButton icon={<Plus />} accessibilityLabel="New" onClick={() => …} />

i18n

KeyDefault
floatButton.backToTop"Back to top"

Props

PropTypeDefaultDescription
accessibilityLabelstringA11y label — falls back to `label`; required when icon-only.
aria-expandedboolean
aria-haspopupboolean | "menu" | "true"Forward ARIA attributes (used internally by Group for `aria-haspopup`/`aria-expanded`).
badgeFloatButtonBadgeBadge — count, dot, or tone passthrough.
childrenReactNodeSet internally when the button lives inside a `<FloatButton.Group>`.
classNamestring
direnumRTL flips left/right placements. @defaultValue 'ltr'
disabledbooleanDisable interaction.
hrefstringAnchor link — renders as an `<a>` on web.
iconReactNodeRequired unless `label` is set.
labelstringInline label — turns the button extended (auto-sets `shape='extended'`).
loadingbooleanShow 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; }) => voidWeb-y alias for `onPress` — same handler, mapped at the call site.
onLongPress(event: GestureResponderEvent) => voidLong-press handler — native first-class, web maps to contextmenu.
onPress(event: GestureResponderEvent) => voidPress 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).
placementenumWhere to anchor the FAB on the screen. @defaultValue 'bottom-right'
positioningenumPositioning 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'
respectSafeAreabooleanOn 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
shapeenumVisual shape. @defaultValue 'circle' (or 'extended' when both icon + label are set)
sizeenumSize — sm=40, md=56, lg=72. @defaultValue 'medium'
testIDstring
tooltipstringTooltip — web: hover/focus tooltip; native (in a group): always-visible label chip.
variantenumsurfaceColor/tone variant. @defaultValue 'primary'
visiblebooleanAnimate in/out. @defaultValue true

On this page

Preview theme