# Floating action button

> A floating main action that opens a speed dial (the plus turns into a close mark, a scrim dims the screen, mini actions rise one by one with labels) and, extended, folds its label away while you scroll.

- Type: Component (actions)
- Access: Free, open source (MIT)
- Page: https://goodui.dev/native/components/fab
- Markdown: https://goodui.dev/native/components/fab.md
- Registry item: https://goodui.dev/native/r/fab.json
- Dependencies: react-native-reanimated, react-native-svg
- Keywords: react native fab, floating action button, speed dial, extended fab, fab collapse on scroll, expo floating button, material fab

## When to use

- One main creating action on a scrolling screen: compose, new task, add expense.
- Two to six related create actions behind one button: transfer, request, top up, split a bill.
- An extended FAB with a label when the action is not obvious from the icon alone.

## When not to use

- Screens with a natural bottom bar for the action: use button in a footer (or the bottom-sheet footer).
- Destructive or secondary actions: they do not float.
- More than six actions or actions with long names: use bottom-sheet with a list.
- Toolbar icons: use icon-button.

## Installation

With the shadcn CLI (needs a components.json in the Expo app), then `npx expo install --fix` so native dependencies match your Expo SDK. The foundation comes along the first time.

```bash
npx shadcn@latest add https://goodui.dev/native/r/fab.json
npx expo install --fix
```

The component lands in `components/goodui-native/fab/` and imports the foundation from `components/goodui-native/foundation/` with relative paths; it also installs locale.

Built-in strings follow `<GooduiLocaleProvider>` (one [language pack](https://goodui.dev/native/r/locale-tr.json) translates the whole kit); the `labels` prop overrides them for one instance.

## Usage

```tsx
import { FlatList, View } from "react-native";
import { Fab, useFabExtended } from "@/components/goodui-native/fab/fab";

export function Inbox({ messages, compose }: { messages: Message[]; compose: () => void }) {
  const { extended, onScroll } = useFabExtended();
  return (
    <View style={{ flex: 1 }}>
      <FlatList data={messages} onScroll={onScroll} scrollEventThrottle={32} renderItem={renderMessage} />
      <Fab accessibilityLabel="Compose" label="Compose" extended={extended} onPress={compose} />
    </View>
  );
}
```

## Examples

### Speed dial

```tsx
<Fab
  accessibilityLabel="New"
  actions={[
    { key: "send", label: "Send money", icon: (c) => <SendIcon color={c} />, onPress: send },
    { key: "request", label: "Request", icon: (c) => <RequestIcon color={c} />, onPress: request },
    { key: "split", label: "Split a bill", icon: (c) => <SplitIcon color={c} />, onPress: split, tone: "accent" },
  ]}
/>
```

### Inside a tab screen with its own bar

```tsx
<Fab accessibilityLabel="Add expense" safeArea={false} offset={16} onPress={add} />
```

## API reference

### Fab

`import { Fab } from "@/components/goodui-native/fab/fab";`

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `accessibilityLabel` (required) | `string` | – | Required: what the button does, for example "New message". |
| `icon` | `FabIcon` | – | Defaults to a plus, which turns into a close mark when a speed dial opens. |
| `label` | `string` | – | Text beside the icon (an extended FAB). |
| `extended` | `boolean` | `true` | Show the label. Set it from scroll direction with `useFabExtended` so the FAB collapses while reading. Default `true`. |
| `onPress` | `() => void` | – | Runs on press when there are no `actions`. |
| `actions` | `FabAction[]` | – | Two to six actions turn the FAB into a speed dial. |
| `open` | `boolean` | – | Controlled speed dial state. |
| `onOpenChange` | `(open: boolean) => void` | – | Called when the speed dial opens or closes. |
| `placement` | `"right" \| "left" \| "center"` | `"right"` | Corner of the parent it sits in. Mirrored under RTL, as React Native does on a device (right means the end side). |
| `variant` | `"primary" \| "secondary"` | `"primary"` | `primary` is the accent fill; `secondary` a raised surface for screens whose main action lives elsewhere. |
| `offset` | `number` | `16` | Distance from the parent's edges, in pt. Default 16. |
| `safeArea` | `boolean` | `true` | Add the bottom and side safe area to `offset`. Turn off inside a tab bar or a card. Default `true`. |
| `scrim` | `boolean` | `true` | Dim the parent behind an open speed dial; tapping it closes the dial. Default `true`. |
| `haptic` | `HapticName \| false` | `"tap"` | Haptic on press. `false` turns it off. |
| `labels` | `Partial<FabLabels>` | – | Overrides for the built-in strings; a GooduiLocaleProvider pack sets them app-wide. |
| `testID` | `string` | – | Identifier for end-to-end tests. |

## Accessibility

- The main button has role button with your accessibilityLabel; with actions it reports expanded and reads 'Close actions' while open.
- Mini actions are buttons labelled with their label; they are hidden from screen readers and touch while the dial is closed.
- The scrim is a 'Close menu' button. Android back closes an open dial before navigating.
- The FAB is 56 pt and mini actions 44 pt, so every target meets 44 pt.

## Motion

- Press: scales to 0.94 on the snappy spring with an overlay, from touch down.
- Open: the default plus rotates 45° into a close mark on the morph spring (a custom icon turns and crossfades into a close mark); the scrim fades in with it.
- Mini actions rise 16 pt and scale from 0.6 on the morph spring, nearest first, 40 ms apart; labels slide in from the side. Closing, the farthest leaves first.
- Extended: the label slot opens and closes its real width on the morph spring; the label fades in after the slot has started opening.
- Reduced motion: no rotation, rise, scale or stagger; icons, actions and the label crossfade or change instantly.

## Haptics

- tap on press (configurable with the haptic prop)
- tap when a speed dial action runs

## Performance

- All motion runs on the UI thread. useFabExtended sets React state only when the scroll direction flips, never per frame.

## Notes for AI

- Render Fab as the last child of a View with flex: 1 that holds the scroll view; it fills that parent with a touch-transparent layer and the scrim covers only that parent.
- accessibilityLabel is required. Leave icon empty to get the plus that morphs into a close mark.
- With actions, onPress is ignored: the press toggles the dial. Each action needs a unique key, a short label and an icon (a function receives the right color).
- Wire extended with useFabExtended: pass onScroll and scrollEventThrottle={32} to the list.
- Inside a tab screen or a card, pass safeArea={false}; the default adds the bottom safe area to offset.
- Use one FAB per screen.

## Related

- [Icon button](https://goodui.dev/native/components/icon-button.md): A layered icon-only button whose surface and icon sink on touch down and release on different springs; it runs async actions itself, carries a badge and shows a hint on long press.
- [Button](https://goodui.dev/native/components/button.md): A press-responsive button with haptics that runs async actions on its own: it gathers into a spinner, then opens into a success or error state.
- [Bottom sheet](https://goodui.dev/native/components/bottom-sheet.md): A sheet that rests at detents, follows the finger 1:1, settles with velocity and hands drags off to its scroll view.

## Guidance for AI tools

Floating action button: A floating main action that opens a speed dial (the plus turns into a close mark, a scrim dims the screen, mini actions rise one by one with labels) and, extended, folds its label away while you scroll. Use only the documented props. Keep accessibility, reduced motion and both color schemes intact when adapting it.

Full library index: https://goodui.dev/native/llms.txt
