FAB
- Stable
- WCAG 2.2 evidence
- RTL
The main action of a mobile screen: a floating button with a
nav/active-bg fill and a large shadow. An Android pattern, usable on web and PWA screens; Figma advises against it on iOS
and on screens with several equal actions (use a Button there). Built on React Aria Button: Enter and Space activate,
disabled buttons leave the tab order, and keyboard focus shows the focus ring (WCAG 2.1.1, 2.4.7, 4.1.2). The name is the
visible label of extended, or aria-label / aria-labelledby for the icon-only sizes (1.1.1). The 40 px Small button has
an extended hit area so it reaches 48 px (2.5.8). Position is yours (the button does not position itself). Typical: fixed bottom-4 end-4 inside a safe-area aware
container, above a NavigationBar with bottom-[calc(env(safe-area-inset-bottom)+5rem)], with bottom padding on the
scrolling content at least the height of the FAB plus its margin, so it never covers the last row or a control at 320 px.
An extended FAB hugs its label and never exceeds its container. Consumer duties: a specific, translated name; why an action is unavailable instead of only disabling it; one FAB per
screen.
import { FAB } from "@nexera-ui/react";- 8
- examples
- 22
- props
- 4
- live controls
- 1
- platform
- 11
- WCAG criteria
- 2
- blocks use it
Try every prop. Copy the code.
Change the props and the code updates. Check light and dark, LTR and RTL, and phone width.
import { FAB } from "@nexera-ui/react";
<FAB />Examples 7
The same examples as Storybook, rendered live. Open Code to copy one.
Regular
Figma Variant=Regular: 56 x 56.
import { FAB } from "@nexera-ui/react";
import { LuPlus } from "react-icons/lu";
<FAB aria-label="Create request" icon={<LuPlus />} variant="regular" />;
Extended
Figma Variant=Extended: icon and label, hugging the label; the label names the button.
import { FAB } from "@nexera-ui/react";
import { LuPlus } from "react-icons/lu";
export function Extended() {
return (
<FAB variant="extended" icon={<LuPlus />}>
Create
</FAB>
);
}
Disabled and loading
Disabled and loading (spec section 7: keep the width and block repeat presses). The name becomes "Create request Loading".
import { FAB } from "@nexera-ui/react";
import { LuPlus } from "react-icons/lu";
export function DisabledAndLoading() {
return (
<div className="flex flex-wrap items-center gap-4">
<FAB aria-label="Create request (disabled)" icon={<LuPlus />} isDisabled />
<FAB aria-label="Create request (loading)" icon={<LuPlus />} variant="regular" isLoading />
<FAB variant="extended" icon={<LuPlus />} isLoading>
Create
</FAB>
</div>
);
}
In screen
Position is the consumer's. The FAB sits at the bottom end of the screen, and the scrolling content has bottom padding at least as tall as the FAB plus its margin (here pb-24), so the last row is never covered, also at 320 px.
import { FAB } from "@nexera-ui/react";
import { LuPlus } from "react-icons/lu";
export function InScreen() {
return (
<div className="relative h-80 max-w-xs overflow-hidden rounded-lg border border-default bg-page">
<div
role="region"
aria-label="Leave requests"
// A scrollable region needs a keyboard route (axe `scrollable-region-focusable`); it is named by `aria-label`.
// eslint-disable-next-line jsx-a11y/no-noninteractive-tabindex
tabIndex={0}
className="h-full overflow-y-auto p-4 pb-24 focus-visible:focus-ring focus-visible:-outline-offset-2"
>
<ul className="m-0 flex list-none flex-col gap-3 p-0">
{Array.from({ length: 8 }, (_, index) => (
<li
key={index}
className="rounded-md border border-default bg-surface p-3 text-body-default text-primary"
>
Leave request {index + 1}
</li>
))}
</ul>
</div>
<FAB variant="extended" icon={<LuPlus />} className="absolute bottom-4 end-4">
Create request
</FAB>
</div>
);
}
Long label
A long translated label wraps and the button stays inside its container.
import { FAB } from "@nexera-ui/react";
import { LuPlus } from "react-icons/lu";
export function LongLabel() {
return (
<div className="max-w-xs">
<FAB variant="extended" icon={<LuPlus />}>
Neuen Urlaubsantrag für das gesamte Team anlegen
</FAB>
</div>
);
}
Right to left
Right to left: the icon sits at the right of the label and the padding follows.
import { FAB } from "@nexera-ui/react";
import { LuPlus } from "react-icons/lu";
export function RightToLeft() {
return (
<div className="flex flex-wrap items-center gap-4">
<FAB aria-label="إنشاء طلب" icon={<LuPlus />} />
<FAB variant="extended" icon={<LuPlus />}>
إنشاء طلب
</FAB>
</div>
);
}
States
The states Figma does not draw (derived from the state tokens), with the React Aria attributes forced on native buttons.
import { IconSlot, fabStyles } from "@nexera-ui/react";
import { LuLoaderCircle, LuPlus } from "react-icons/lu";
const stateAttributes = {
default: {},
hover: { "data-hovered": true },
pressed: { "data-pressed": true },
focused: { "data-focus-visible": true },
disabled: { "data-disabled": true, disabled: true },
loading: { "data-pending": true, "aria-disabled": true },
} as const;
export function States() {
return (
<div className="flex flex-wrap items-center gap-6 p-4">
{(Object.keys(stateAttributes) as (keyof typeof stateAttributes)[]).map((state) => (
<button
key={state}
type="button"
aria-label={`Create request (${state})`}
className={fabStyles({ variant: "regular" })}
{...stateAttributes[state]}
>
<IconSlot className={fabIconStyles({ variant: "regular" })}>
{state === "loading" ? (
<LuLoaderCircle className="size-full motion-safe:animate-nx-spin" />
) : (
<LuPlus />
)}
</IconSlot>
</button>
))}
</div>
);
}
Props 22
Press "Try it" on a card to load that prop into the playground.
22 props shown
icon*NexeraReactNodeThe icon: 20 px on small, 24 px on regular and extended.
Decorative: the accessible name comes from the label, aria-label or aria-labelledby. Wrap direction-bearing glyphs
in DirectionalIcon so they flip in right-to-left layouts.
isLoadingNexerabooleanLoading state (spec section 7: "keep width stable and block repeat presses"). Replaces the icon with a spinner, stops
presses and form submission, keeps focus and the size, and adds loadingLabel to the accessible name (announced while
focused).
loadingLabelNexerastringText added to the accessible name while isLoading is true ("Create Loading"). Translate it for your locale.
classNameNexerastringExtra classes, merged last so they win over the defaults.
styleNexeraCSSPropertiesInline style.
variantNexera"extended" | "small" | "regular"Size. extended is a 56 px pill with an icon and a label.
Size: small (40 px, the default) or regular (56 px). Both are icon-only and need an accessible name.
childrenNexeraReactNodeVisible label: a verb that names the outcome ("Create request"), one to
three words. It names the button and wraps instead of truncating.
Icon-only buttons have no visible label; use variant="extended" to show one.
aria-labelNexerastringAccessible name when it must differ from the visible label. Prefer the visible label (WCAG 2.5.3).
Accessible name (WCAG 1.1.1, 4.1.2). Required unless aria-labelledby is set. Name the outcome ("Create request"),
translate it, and show the same text in a Tooltip when the icon alone is not obvious.
Accessible name. Optional when aria-labelledby is set.
aria-labelledbyNexerastringId(s) of visible element(s) that name the button.
Id(s) of visible element(s) that name the button; wins over aria-label.
isDisabledReact AriabooleanWhether the button is disabled.
onPressReact Aria(e: PressEvent) => voidHandler that is called when the press is released over the target.
onPressStartReact Aria(e: PressEvent) => voidHandler that is called when a press interaction starts.
onPressEndReact Aria(e: PressEvent) => voidHandler that is called when a press interaction ends, either over the target or when the pointer leaves the target.
onPressChangeReact Aria(isPressed: boolean) => voidHandler that is called when the press state changes.
onPressUpReact Aria(e: PressEvent) => voidHandler that is called when a press is released over the target, regardless of whether it started on the target or not.
autoFocusReact AriabooleanWhether the element should receive focus on render.
typeReact Aria"button" | "submit" | "reset"The behavior of the button when used in an HTML form.
formReact AriastringThe <form> element to associate the button with.
The value of this attribute must be the id of a <form> in the same document.
See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Reference/Elements/button#form).
nameReact AriastringSubmitted as a pair with the button's value as part of the form data.
valueReact AriastringThe value associated with the button's name when it's submitted with the form data.
idReact AriastringThe element's unique identifier. See [MDN](https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/id).
aria-describedbyReact AriastringIdentifies the element (or elements) that describes the object.
* Required. React Aria props shown are the ones most apps use; the component accepts the rest of its React Aria props too.
Accessible by default.
Built on React Aria, and covered by the WCAG 2.2 evidence generated on every build.
WCAG 2.2 evidence
7 direct · 4 supporting- 1.1.1Non-text ContentLevel A · tested directly
- 1.3.1Info and RelationshipsLevel A · tested directly
- 1.3.2Meaningful SequenceLevel A · supporting test
- 1.4.4Resize TextLevel AA · supporting test
- 1.4.12Text SpacingLevel AA · supporting test
- 2.1.1KeyboardLevel A · tested directly
- 2.4.7Focus VisibleLevel AA · tested directly
- 2.5.3Label in NameLevel A · tested directly
- 2.5.8Target Size (Minimum)Level AA · supporting test
- 4.1.2Name, Role, ValueLevel A · tested directly
- 4.1.3Status MessagesLevel AA · tested directly
Fits any width.
Nexera components respond to the space they are given. Drag the corner of the frame, or pick a width.
Styling hooks
Pass className to add Tailwind classes (merged last). State is exposed as data attributes, so you can style it with variants like data-pressed:.
data-disableddata-hovereddata-pendingdata-pressed
<FAB className="data-disabled:opacity-90 shadow-sm" />Used in blocks
Related components
- BreadcrumbShows where the current page sits in the hierarchy and links back to each level.
- BreadcrumbItemOne level of a `Breadcrumb` trail; use it only inside `Breadcrumb`.
- CursorPagerA previous and next pair for data without stable page numbers, such as an activity log or a cursor API.
- FeedStatusThe status line at the bottom of an infinite feed or a lazy list: "Loading more", "You have reached the end" or "Could not load more" with a retry action.
- JumpToPageA small "Go to page [48] of 120" control for long paged lists.
- NavigationBarThe bottom bar of a mobile layout: a named `nav` landmark with a list of three to five `NavItem`s.
- NavItemOne destination or action of a `NavigationBar`.
- PageButtonOne page number of a `Pagination` or `TableFooter`.