Fab

One primary action floating over the screen, extended with a label or unfolding a dial.

Open Source

Installation

Fab ships with the library — install it once, then import:

import { Fab } from "@delacour/react-native-ui/fab";

Or copy the source into your project, to own and edit it:

bunx delacour@latest add fab

Usage

app/index.tsx
import { Fab } from "@/components/ui/fab";
import { Icon } from "@/components/ui/icon";
import { Text } from "@/components/ui/text";
import { IconPlusLarge } from "@/lib/icons/central";
import { useState } from "react";
import { ScrollView, View } from "react-native";

export default function Example() {
  const [notes, setNotes] = useState(["Groceries", "Garden plan"]);

  return (
    <View className="flex-1 bg-background">
      <ScrollView contentContainerClassName="p-4 pb-28">
        {notes.map((note) => (
          <Text className="py-3" key={note}>
            {note}
          </Text>
        ))}
      </ScrollView>
      <Fab
        accessibilityLabel="New note"
        onPress={() => setNotes((current) => [...current, `Note ${current.length + 1}`])}
        placement="bottom-end"
      >
        <Icon icon={IconPlusLarge} />
      </Fab>
    </View>
  );
}

Anatomy

<Fab accessibilityLabel="New note" placement="bottom-end">
  <Icon icon={IconPlusLarge} />
</Fab>

<Fab isExtended>
  <Icon icon={IconPencil} />
  <Fab.Label>Write</Fab.Label>
</Fab>

<Fab.Group accessibilityLabel="Add something" icon={IconPlusLarge}>
  <Fab.Action icon={IconImages1} label="Photo" onPress={addPhoto} />
</Fab.Group>

A Fab composes its icon the way a Button does, and a bare <Icon /> inside it takes the fab's size and foreground. Fab.Group and Fab.Action take an icon prop instead, because they draw and turn the glyph themselves.

Placement

placement pins the fab to the bottom of its nearest positioned ancestor; without it the fab sits in flow like any other view.

Placements
Placements
<Fab accessibilityLabel="Record" placement="bottom-start">…</Fab>
<Fab accessibilityLabel="Add" placement="bottom-center">…</Fab>
<Fab accessibilityLabel="Send" offset={24} placement="bottom-end">…</Fab>

A pinned fab covers the bottom of your list. Pad the scroll content's bottom by the fab's height plus its offset — pb-28 clears a medium fab — or the last row can never scroll out from under it.

Over a list

Safe area

A pinned fab adds the bottom safe-area inset to offset. Turn that off with isSafeAreaAware={false} when the fab sits in a box that already clears the home indicator, such as above a tab bar.

<Fab accessibilityLabel="Compose" isSafeAreaAware={false} placement="bottom-end">…</Fab>

Sizes

sm, md and lg are 44, 56 and 64pt, from the fab's own size tokens.

Sizes and variants
Sizes and variants
<Fab accessibilityLabel="Add" size="sm">…</Fab>
<Fab accessibilityLabel="Add" size="md">…</Fab>
<Fab accessibilityLabel="Add" size="lg">…</Fab>

Variants

primary is the default; secondary, surface and destructive paint the circle with the matching token.

<Fab accessibilityLabel="Add" variant="primary">…</Fab>
<Fab accessibilityLabel="Add" variant="secondary">…</Fab>
<Fab accessibilityLabel="Add" variant="surface">…</Fab>
<Fab accessibilityLabel="Delete" variant="destructive">…</Fab>

Extended

isExtended makes a stadium with a Fab.Label beside the icon, and the label names the button.

Extended
Extended
<Fab isExtended placement="bottom-end" onPress={write}>
  <Icon icon={IconPencil} />
  <Fab.Label>Write</Fab.Label>
</Fab>

Speed dial

Fab.Group unfolds a dial of related actions over a scrim; pressing an action runs it and closes the dial.

Speed dial
<Fab.Group accessibilityLabel="Add something" icon={IconPlusLarge}>
  <Fab.Action icon={IconImages1} label="Photo" onPress={addPhoto} />
  <Fab.Action icon={IconPaperclip1} label="Attachment" onPress={attach} />
  <Fab.Action icon={IconTrashCan} isDestructive label="Empty drafts" onPress={emptyDrafts} />
</Fab.Group>

Fab.Group covers its parent with the scrim, so write it in the screen's root container, beside the scroll view rather than inside its content.

Controlled dial

isOpen with onOpenChange hands the dial's state to the screen; label extends the trigger while it is closed.

const [isOpen, setIsOpen] = useState(false);

<Fab.Group
  accessibilityLabel="Create"
  icon={IconPlusLarge}
  isOpen={isOpen}
  label="Create"
  onOpenChange={setIsOpen}
>
  <Fab.Action icon={IconNoteText} label="Note" onPress={newNote} />
</Fab.Group>

Accessibility

Every fab and action is a button. A round fab has no text to read, so give it an accessibilityLabel — it warns in development without one. The group trigger announces whether the dial is expanded, the open dial is modal, and Android's back button closes it.

API Reference

Fab

Extends the Pressable's props, except disabled and busy.

Prop

Type

Fab.Label

The extended fab's text. Extends React Native's TextProps.

Prop

Type

Fab.Group

A trigger, a scrim and a dial of Fab.Actions. Extends ViewProps.

Prop

Type

Fab.Action

One entry in the dial: a small round button and a label chip.

Prop

Type

useFab

Returns { size, variant, isExtended, isDisabled } for a custom child of a Fab. Throws outside one.

On this page