{
  "title": "FloatButton",
  "description": "Cross-platform Floating Action Button — standalone, grouped (speed-dial), or back-to-top — with safe-area-aware positioning, badge, and full a11y.",
  "url": "/docs/components/float-button",
  "since": "0.0.7",
  "tags": [
    "actions",
    "navigation"
  ],
  "platform": "both",
  "source": "---\ntitle: FloatButton\ndescription: Cross-platform Floating Action Button — standalone, grouped (speed-dial), or back-to-top — with safe-area-aware positioning, badge, and full a11y.\nsince: 0.0.7\ntags: [actions, navigation]\nplatform: both\ncategory: actions\n---\n\nimport { BundleSize } from '@/components/bundle-size';\nimport { Preview } from '@/components/preview';\nimport { PropsTable } from '@/components/props-table';\n\n<BundleSize component=\"FloatButton\" />\n\n## At a glance\n\n- **Three sub-components**: `<FloatButton>` standalone, `<FloatButton.Group>` for click/long-press expansion, and `<FloatButton.BackToTop>` preset.\n- **Three shapes**: `circle | square | extended` (extended = pill with inline label) at three sizes (`sm` 40, `md` 56, `lg` 72).\n- **Four variants**: `primary | secondary | tertiary | surface` mapped to our semantic tokens.\n- **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}`).\n- **Badge slot** — `badge={{ count, dot, tone }}` forwards to our `<Badge>` and pins to the top-right of the button.\n- **Group with click or long-press trigger**, controlled or uncontrolled (`open` / `defaultOpen` / `onOpenChange`), optional `backdrop` scrim that closes on tap.\n- **`actions` array OR children** — the same dual API as Pagination / Breadcrumb. Agents reach for the array; humans reach for JSX.\n- **`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).\n- **`href`** integration on web (renders an `<a>`). Optional **`positioning='absolute'`** lets you contain the FAB inside a positioned ancestor (used by these docs previews).\n- **A11y**: `role=\"button\"` + `aria-haspopup=\"menu\"`, `aria-expanded`, `aria-busy`; required `accessibilityLabel` for icon-only buttons (dev warning).\n\n## Preview\n\n<Preview name=\"float-button-basic\" />\n\n## Quick props reference\n\n| Prop                | Values                                                  |\n|---------------------|---------------------------------------------------------|\n| `shape`             | `circle` (default), `square`, `extended` (auto-pill with label) |\n| `size`              | `sm` (40 px), `md` (56 px, default), `lg` (72 px)       |\n| `variant`           | `primary`, `secondary`, `tertiary`, `surface`           |\n| `tooltip`           | String shown next to the trigger on hover (web) / long-press (native) |\n| `dir`               | `ltr` (default) / `rtl` — flips placement left/right    |\n| `visible`           | Controls render — when `false` the FAB exits with a fade |\n| `onLongPress`       | Long-press handler — fires after the configured hold    |\n\n## Shapes\n\n`circle` (default), `square` (rounded square — AntD parity), `extended` (capsule with inline label — auto-applies when both `icon` and `label` are present).\n\n<Preview name=\"float-button-shapes\" />\n\n## Variants\n\nEach variant maps to semantic interactive tokens. `surface` is the canonical \"less attention-grabbing\" variant — used by `BackToTop`.\n\n<Preview name=\"float-button-variants\" />\n\n## Badge\n\n`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.\n\n<Preview name=\"float-button-badge\" />\n\n## Group — vertical stack\n\n`<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.\n\n<Preview name=\"float-button-group\" />\n\n### With backdrop\n\n`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.\n\n<Preview name=\"float-button-group-backdrop\" />\n\n## BackToTop\n\nPreset 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.\n\n<Preview name=\"float-button-back-to-top\" />\n\n## Positioning\n\n`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).\n\n| Prop | Type | Default | Notes |\n|---|---|---|---|\n| `placement` | `'top-left' \\| 'top-right' \\| 'bottom-left' \\| 'bottom-right'` | `'bottom-right'` | RTL flips left/right. |\n| `offset` | `{ x?: number; y?: number }` | `{ x: 24, y: 24 }` web, `{ x: 16, y: 16 }` native | Px from the placement corner. |\n| `respectSafeArea` | `boolean` | `true` | Native-only — auto-adds `useSafeAreaInsets().bottom`. |\n| `positioning` | `'fixed' \\| 'absolute'` | `'fixed'` | Use `'absolute'` to contain inside a positioned parent. |\n\n## Accessibility\n\n- The button uses native `role=\"button\"` (web) / `accessibilityRole=\"button\"` (native).\n- `accessibilityLabel` is required for icon-only buttons — a dev-only warning fires if missing.\n- `aria-busy=\"true\"` while `loading`; `aria-disabled=\"true\"` while `disabled`.\n- `<FloatButton.Group>` adds `aria-haspopup=\"menu\"` + `aria-expanded` to the trigger.\n- The backdrop is a `<Pressable>` with the localized \"Close\" label.\n- 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).\n\n## Press handlers — `onPress` vs `onClick`\n\nWe'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.\n\n```tsx\n<FloatButton icon={<Plus />} accessibilityLabel=\"New\" onPress={() => …} />\n\n// Both are accepted, but onClick is silently shadowed if onPress is set:\n<FloatButton icon={<Plus />} accessibilityLabel=\"New\" onClick={() => …} />\n```\n\n## i18n\n\n| Key | Default |\n|---|---|\n| `floatButton.backToTop` | \"Back to top\" |\n\n## Props\n\n<PropsTable component=\"FloatButton\" />\n"
}
