FAB React component for Tailwind CSS v4
v1.0.0Added in 1.0.0
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.
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.
FAB is a Pro React component in the React mobile components category, written in TypeScript for React 19 and Tailwind CSS v4. It installs as source with npx wingo-ui@latest add fab, together with Tooltip, Navigation, useScrollState, useKeyboardInset, useShortcut and 5 more and the npm packages motion and lucide-react.
Add to a project
Requirements: React 19 and Tailwind CSS v4, in Next.js (App Router or Pages), Vite or React Router. Installation guide · Next.js setup · Vite setup
npx wingo-ui@latest add fabFAB is part of Pro, with 298 more Pro items: $8 a month or $150 once. 14-day refund before you download any Pro item. See plans
Usage
Import Fab from @/components/ui/fab.
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
labeldefault""stringThe extended label ("Add") and the button's accessible name. Without it the name is labels.open / labels.close with actions, else labels.action.
icondefault"<Plus />"ReactNodeThe button's icon. Unsized svgs follow the size (20 / 24 / 28px).
openIconReactNodeShown while the dial is open. Default: the plus turns 45 degrees into an x; a custom icon cross-fades to an <X />.
actionsdefault"[]"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>) => voidThe action when there is no dial. With actions it runs before the dial toggles; preventDefault keeps it closed.
layoutdefault"vertical""vertical" | "radial"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.
extendeddefault"auto""auto" | "always" | "never"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.
variantdefault"solid""solid" | "soft" | "surface"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).
tonedefault"neutral""neutral" | "brand" | "success" | "warning" | "danger" | "info"Semantic color. neutral is black in light mode and white in dark mode; brand is for the one hero action of a marketplace.
colordefault""stringAny CSS color. Replaces the tone; text on a solid fill turns black or white by lightness.
sizedefault"md""sm" | "md" | "lg"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).
radiusdefault"full""none" | "sm" | "md" | "lg" | "full"Corner shape, 0 / 12 / 16 / 20px at md and scaled per size; full is a circle, a pill when extended. Dial actions follow.
defaultOpendefaultfalsebooleanStarts with the dial open. Control it with open and onOpenChange.
openbooleanThe dial's open state, controlled.
onOpenChange(open: boolean) => voidRuns when the dial opens or closes.
positiondefault"fixed""fixed" | "sticky" | "absolute"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).
sidedefault"end""end" | "start" | "center"Logical side, so end is right in left-to-right pages and left in right-to-left ones. center opens a half-arc dial.
offsetdefault16numberpx 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.
hideOnScrolldefaultfalsebooleanSlides out while you scroll down, following the finger, and comes back when you scroll up. Never while open or focused.
hideOnKeyboarddefaulttruebooleanSteps aside while the on-screen keyboard is open (and closes an open dial), so it never covers the field you type in.
scrimdefaulttruebooleanDims and softly blurs the page behind an open dial; a tap on it closes. Without it a press anywhere else closes.
closeOnActiondefaulttruebooleanCloses the dial after an action runs. Actions with href always close.
hapticsdefaulttruebooleanA light tick when the dial opens and when an action runs, on touch only (never for a mouse click or a key).
shortcutdefault""stringA 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.
safeAreadefaulttruebooleanKeeps clear of the home indicator and the notch with env(safe-area-inset-*). The insets are 0 on desktops.
scrollTargetHTMLElement | Window | RefObject<HTMLElement | null> | nullWhat scrolls, for extended="auto" and hideOnScroll. Default: the nearest ScrollContainerProvider, else (sticky and absolute) the scroll parent, else the window.
linkComponent(props: LinkComponentProps) => ReactNodeThe router link for actions with href. Default: the LinkProvider's component, else a plain <a>.
labelsdefault"DEFAULT_FAB_LABELS"Partial<FabLabels>Screen reader strings: open "Open actions", close "Close actions", menu "Actions", action "Create". Pass only the keys you translate.
classNamesPartial<Record<"scrim" | "actions" | "action" | "actionLabel" | "actionIcon" | "trigger" | "icon" | "label", string>>Extra classes for the inner parts. className goes on the positioned root.
Parts
FabAction
typeOne speed dial action: id, label, icon, onSelect or href (a router link that closes the dial), tone ("neutral" | "danger") and disabled.
DEFAULT_FAB_LABELS
constantThe 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.
Dependencies
Files
- components/ui/fab.tsx
FAQ
Is the React FAB component free?
The preview, props and docs are free; copying the source needs a Pro plan. See plans.
How do I install FAB?
Run npx wingo-ui@latest add fab. It also adds the Wingo UI items it builds on (Tooltip, Navigation, useScrollState, useKeyboardInset, useShortcut and 5 more) and the npm packages motion and lucide-react. With shadcn, add the @wingo-ui registry with your API key, then run npx shadcn@latest add @wingo-ui/fab.
See also
More React mobile components
Articles using this component
- React Floating Action Button: Speed Dial and Safe Areas
Mobile UI in React9 min read
- React Bottom Navigation Bar in Next.js: Safe Areas, Badges
Mobile UI in React8 min read
- Tailwind Safe Area Insets: Bottom Bars, FABs and Toasts
Mobile UI in React12 min read