# FAB

> A React floating action button (FAB) for a phone screen's main action, with a labeled or radial speed dial and a label that folds on scroll.

- Type: React component (Mobile)
- Access: Pro (the source needs a Wingo UI Pro plan, see https://wingo-ui.com/pricing)
- Version: 1.0.0
- Install: `npx wingo-ui@latest add fab`
- Docs: https://wingo-ui.com/components/fab

## Interaction

Tap the round button in the thumb zone. Without actions it runs its one action; with actions the page dims and a speed dial rises from it, nearest action first, while the plus turns into an x and the phone ticks once. Vertical dials show a label chip beside each round action and the whole row is the target; radial dials fly small round actions out on an arc (a half arc when centered) and show each label as a tooltip on hover, focus or a long press. Tap an action to run it and close the dial, tap the dimmed page or the x to close without choosing. With a label the button starts extended ("Add"); scroll down and it folds to the icon, scroll up and the label comes back. With hideOnScroll it slides out while you scroll down and returns when you scroll up, never while it is open or focused, and it steps aside while the on-screen keyboard is up. It sits above the bottom tab bar and the sticky buy bar and clears the home indicator (once, never twice). Keyboard: Enter, Space or ArrowUp open the dial and focus the nearest action; ArrowUp / ArrowDown move (radial dials also Left / Right), Home / End jump, Enter or Space runs, Escape closes and returns focus to the button, Tab closes and moves on. shortcut opens it from anywhere. With a mouse the button and every action tint on hover; while the button shows only its icon (or has a shortcut) hovering or focusing it shows its name and the shortcut in a tooltip, and a long press shows it on touch.

## Installation

```bash
npx wingo-ui@latest add fab
```

Pro item: run `npx wingo-ui@latest login` once with a Pro account. The CLI adds the files, their dependencies and a version record for updates.

With shadcn: `npx shadcn@latest add @wingo-ui/fab` (needs the @wingo-ui registry with your API key in components.json)

Files:

- `components/ui/fab.tsx`

npm dependencies: `npm i motion lucide-react`

Wingo UI dependencies (installed automatically):

- [Tooltip](https://wingo-ui.com/components/tooltip) ([Markdown](https://wingo-ui.com/components/tooltip.md))
- [Navigation](https://wingo-ui.com/components/navigation) ([Markdown](https://wingo-ui.com/components/navigation.md))
- [useScrollState](https://wingo-ui.com/components/use-scroll-state) ([Markdown](https://wingo-ui.com/components/use-scroll-state.md))
- [useKeyboardInset](https://wingo-ui.com/components/use-keyboard-inset) ([Markdown](https://wingo-ui.com/components/use-keyboard-inset.md))
- [useShortcut](https://wingo-ui.com/components/use-shortcut) ([Markdown](https://wingo-ui.com/components/use-shortcut.md))
- [useControllableState](https://wingo-ui.com/components/use-controllable-state) ([Markdown](https://wingo-ui.com/components/use-controllable-state.md))
- [Haptics](https://wingo-ui.com/components/haptics) ([Markdown](https://wingo-ui.com/components/haptics.md))
- [Motion presets](https://wingo-ui.com/components/motion) ([Markdown](https://wingo-ui.com/components/motion.md))
- [Tones](https://wingo-ui.com/components/tones) ([Markdown](https://wingo-ui.com/components/tones.md))
- [UIProvider](https://wingo-ui.com/components/ui-config) ([Markdown](https://wingo-ui.com/components/ui-config.md))

## Usage

```tsx
import { FileText, Handshake, StickyNote, UserPlus } from "lucide-react"
import { Fab, type FabAction } from "@/components/ui/fab"

export function ClientsFab({ onCreate }: { onCreate: (kind: string) => void }) {
  const actions: FabAction[] = [
    { id: "client", label: "New client", icon: <UserPlus />, onSelect: () => onCreate("client") },
    { id: "invoice", label: "New invoice", icon: <FileText />, onSelect: () => onCreate("invoice") },
    { id: "quote", label: "New quote", icon: <Handshake />, href: "/quotes/new" },
    { id: "note", label: "Note", icon: <StickyNote />, onSelect: () => onCreate("note") },
  ]
  return (
    <Fab
      label="Add"
      actions={actions}
      shortcut="n"
      labels={{ open: "Show create actions", close: "Hide create actions", menu: "Create" }}
    />
  )
}
```

## Props

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `label?` | `string` | `""` | The extended label ("Add") and the button's accessible name. Without it the name is labels.open / labels.close with actions, else labels.action. |
| `icon?` | `ReactNode` | `"<Plus />"` | The button's icon. Unsized svgs follow the size (20 / 24 / 28px). |
| `openIcon?` | `ReactNode` |  | Shown while the dial is open. Default: the plus turns 45 degrees into an x; a custom icon cross-fades to an <X />. |
| `actions?` | `FabAction[]` | `"[]"` | The speed dial, 2 to 6 entries: { id, label, icon, onSelect?, href?, tone?: "neutral" \| "danger", disabled? }. Actions with href render a NavLink and close the dial. Without actions the FAB is a plain button (onClick). |
| `onClick?` | `(event: MouseEvent<HTMLButtonElement>) => void` |  | The action when there is no dial. With actions it runs before the dial toggles; preventDefault keeps it closed. |
| `layout?` | `"vertical" \| "radial"` | `"vertical"` | vertical: a column of labeled actions above the button (Material). radial: small round actions on a quarter arc around it (a half arc when centered), labels as tooltips. |
| `extended?` | `"auto" \| "always" \| "never"` | `"auto"` | Needs a label. auto: the label shows at the top of the page and while scrolling up, and folds to the icon while scrolling down. always keeps it, never shows the icon alone. |
| `variant?` | `"solid" \| "soft" \| "surface"` | `"solid"` | solid: filled with the tone. soft: a tinted fill that stays opaque over the page. surface: a popover-colored button with a toned icon (Material 3 surface FAB). |
| `tone?` | `"neutral" \| "brand" \| "success" \| "warning" \| "danger" \| "info"` | `"neutral"` | Semantic color. neutral is black in light mode and white in dark mode; brand is for the one hero action of a marketplace. |
| `color?` | `string` | `""` | Any CSS color. Replaces the tone; text on a solid fill turns black or white by lightness. |
| `size?` | `"sm" \| "md" \| "lg"` | `"md"` | Button 48 / 56 / 64px with 20 / 24 / 28px icons and a 14 / 15 / 16px label; dial actions 40 / 44 / 48px (sm grows to 44px on touch). |
| `radius?` | `"none" \| "sm" \| "md" \| "lg" \| "full"` | `"full"` | Corner shape, 0 / 12 / 16 / 20px at md and scaled per size; full is a circle, a pill when extended. Dial actions follow. |
| `defaultOpen?` | `boolean` | `false` | Starts with the dial open. Control it with open and onOpenChange. |
| `open?` | `boolean` |  | The dial's open state, controlled. |
| `onOpenChange?` | `(open: boolean) => void` |  | Runs when the dial opens or closes. |
| `position?` | `"fixed" \| "sticky" \| "absolute"` | `"fixed"` | fixed to the viewport (the app default); sticky to the bottom of its scroll container (render it as the last child: phone frames, panels); absolute inside a relative container (a map, a card). |
| `side?` | `"end" \| "start" \| "center"` | `"end"` | Logical side, so end is right in left-to-right pages and left in right-to-left ones. center opens a half-arc dial. |
| `offset?` | `number` | `16` | px from the side and from what is below it: the bottom tab bar (--bottom-bar-offset), the sticky buy bar (--cta-bar-offset) and the home indicator. |
| `hideOnScroll?` | `boolean` | `false` | Slides out while you scroll down, following the finger, and comes back when you scroll up. Never while open or focused. |
| `hideOnKeyboard?` | `boolean` | `true` | Steps aside while the on-screen keyboard is open (and closes an open dial), so it never covers the field you type in. |
| `scrim?` | `boolean` | `true` | Dims and softly blurs the page behind an open dial; a tap on it closes. Without it a press anywhere else closes. |
| `closeOnAction?` | `boolean` | `true` | Closes the dial after an action runs. Actions with href always close. |
| `haptics?` | `boolean` | `true` | A light tick when the dial opens and when an action runs, on touch only (never for a mouse click or a key). |
| `shortcut?` | `string` | `""` | A key combo that opens the dial (or runs the action), e.g. "n" or "mod+shift+n". Shown in the button's tooltip and set as aria-keyshortcuts. |
| `safeArea?` | `boolean` | `true` | Keeps clear of the home indicator and the notch with env(safe-area-inset-*). The insets are 0 on desktops. |
| `scrollTarget?` | `HTMLElement \| Window \| RefObject<HTMLElement \| null> \| null` |  | What scrolls, for extended="auto" and hideOnScroll. Default: the nearest ScrollContainerProvider, else (sticky and absolute) the scroll parent, else the window. |
| `linkComponent?` | `(props: LinkComponentProps) => ReactNode` |  | The router link for actions with href. Default: the LinkProvider's component, else a plain <a>. |
| `labels?` | `Partial<FabLabels>` | `"DEFAULT_FAB_LABELS"` | Screen reader strings: open "Open actions", close "Close actions", menu "Actions", action "Create". Pass only the keys you translate. |
| `classNames?` | `Partial<Record<"scrim" \| "actions" \| "action" \| "actionLabel" \| "actionIcon" \| "trigger" \| "icon" \| "label", string>>` |  | Extra classes for the inner parts. className goes on the positioned root. |

## Parts

### FabAction (type)

One speed dial action: id, label, icon, onSelect or href (a router link that closes the dial), tone ("neutral" | "danger") and disabled.

### DEFAULT_FAB_LABELS (constant)

The English screen reader strings: open "Open actions", close "Close actions", menu "Actions", action "Create".

## Examples

- **Radial dial over a map**: Three round actions fly out on a quarter arc; hover, focus or long-press one for its label.
- **Compose button**: No actions: the FAB is the action. The label shows at the top and folds while you read down the inbox.
- **Above a bottom tab bar**: Both sticky in one scroller: the tab bar writes --bottom-bar-offset and the FAB sits on top of it.
- **Sizes and radius**: 48, 56 and 64px, as a circle or a rounded square.
- **Surface variant in brand**: The Material 3 surface FAB: a popover-colored button with a brand icon and label for the marketplace's one hero action.
- **Centered half-arc dial**: A centered FAB spreads its actions over a half arc; arrow keys move left and right along it.
- **Hide on scroll**: Slides away while you scroll down and returns on scroll up, never while open or focused.

## See also

- [Bottom Tab Bar](https://wingo-ui.com/components/bottom-tab-bar) ([Markdown](https://wingo-ui.com/components/bottom-tab-bar.md))
- [Sticky CTA Bar](https://wingo-ui.com/components/sticky-cta-bar) ([Markdown](https://wingo-ui.com/components/sticky-cta-bar.md))
- [Back To Top](https://wingo-ui.com/components/back-to-top) ([Markdown](https://wingo-ui.com/components/back-to-top.md))
- [Action Sheet](https://wingo-ui.com/components/action-sheet) ([Markdown](https://wingo-ui.com/components/action-sheet.md))
- [useScrollState](https://wingo-ui.com/components/use-scroll-state) ([Markdown](https://wingo-ui.com/components/use-scroll-state.md))
- [Haptics](https://wingo-ui.com/components/haptics) ([Markdown](https://wingo-ui.com/components/haptics.md))

---

Canonical: https://wingo-ui.com/components/fab
More components: https://wingo-ui.com/components · Index for agents: https://wingo-ui.com/llms.txt
