FloatButton
An action that stays reachable: pinned to a corner, with the offsets, stacking and disclosure a stack of them needs.
@luc/uiExamples
Pinned to a surface
Pinned to the viewport by default, and to the nearest positioned ancestor with `fixed={false}`. It sits at a layer below every overlay, so a dialog covers it instead of fighting it.
Preview surface
<FloatButton icon={<RiDownloadLine />} label="Export this view" />
// Inside a surface instead of the viewport:
<div className="relative">
<FloatButton fixed={false} icon={<RiDownloadLine />} label="Export" />
</div>A stack that opens
The trigger is a real button with `aria-expanded`; opening moves focus to the first action, and Escape closes the stack and returns focus to the trigger.
Quick actions, opened
<FloatButtonGroup
label="Quick actions"
icon={<RiSparklingLine />}
actions={[
{ id: "refresh", icon: <RiRefreshLine />, label: "Re-run" },
{ id: "settings", icon: <RiSettings3Line />, label: "Settings" },
{ id: "share", icon: <RiShareLine />, label: "Share" },
]}
/>All four corners
`side` picks the corner, and the offset clears the device's own safe area, so a floating action does not land on a phone's home indicator.
bottom-right
bottom-left
top-right
top-left
<FloatButton icon={<RiDownloadLine />} label="Export" />
<FloatButton side="bottom-left" icon={<RiDownloadLine />} label="Export" />
<FloatButton side="top-right" icon={<RiDownloadLine />} label="Export" />
<FloatButton side="top-left" icon={<RiDownloadLine />} label="Export" />Intents, sizes and a count
The badge is decoration: it is `aria-hidden`, so a number a reader has to hear belongs in `label`, in the caller's own language.
accent, badge 3
solid, md
danger
<FloatButton
variant="accent"
icon={<RiRefreshLine />}
label="Jobs finished since you last looked: 3"
badge={3}
/>
<FloatButton size="md" icon={<RiShareLine />} label="Share" />
<FloatButton variant="danger" icon={<RiDeleteBinLine />} label="Discard draft" />Usage
import { FloatButton } from "@luc/ui";<FloatButton icon={<RiDownloadLine />} label="Export this view" />Package setup — peer requirements, the stylesheet and the provider — is on installation.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
FloatButton | 8 documented props | ({ icon, label, side, variant, size, badge, fixed, className, ...props }: FloatButtonProps) => import("@types/react").JSX.Element |
FloatButtonGroup | 7 documented props | ({ icon, label, side, actions, defaultOpen, fixed, className, }: FloatButtonGroupProps) => import("@types/react").JSX.Element |
FloatButton props
Type: FloatButtonProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
badge? | number | undefined | — | A count on the control. Decorative: put the meaning in `label` if a screen reader has to hear it. |
className? | string | undefined | — | The stack this control belongs to, when one is placed beside another. |
fixed? | boolean | undefined | — | Pinned to the viewport (default). `false` positions it inside the nearest `relative` ancestor instead — a canvas, a preview pane, a card. |
icon | ReactNode | — | |
label | string | — | Required: an icon-only control must have an accessible name. |
side? | FloatButtonSide | undefined | — | Which corner of the viewport (or of the positioned ancestor). |
size? | "md" | "lg" | undefined | — | |
variant? | "danger" | "accent" | "solid" | undefined | — | `solid` is the neutral action, `accent` the primary one. |
FloatButtonGroup props
Type: FloatButtonGroupProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
actions | readonly FloatButtonAction[] | — | |
className? | string | undefined | — | |
defaultOpen? | boolean | undefined | — | Open on first paint. Rare; useful in a screenshot or a test. |
fixed? | boolean | undefined | — | |
icon? | ReactNode | — | The trigger's icon while closed. |
label | string | — | The trigger's accessible name — what this stack of actions is for, e.g. "Quick actions". Not a menu title: the trigger is the only thing named. |
side? | FloatButtonSide | undefined | — |
Guidelines
Do
- Keep one floating action per surface: a second one in the same corner makes both harder to find.
- Use `FloatButtonGroup` for two to four related actions and let it own the disclosure, rather than placing sibling floats by hand.
- Pass `fixed={false}` inside a preview, a canvas or a card, so the action is positioned by that surface instead of the viewport.
Don't
- Float an action to a corner with no label: the corner is easy to hit and easy to misread.
- Put a count in the badge and nowhere else — it is `aria-hidden`, so a number a reader has to hear belongs in `label`.
- Add your own `fixed` or `absolute` positioning: the component already sets the corner, the safe-area inset and the layer.
Accessibility
The control is the same `IconButton` as every other icon-only action, with a required `label`; the group's trigger is a real button carrying `aria-expanded` and `aria-controls`, opening moves focus to the first action, and Escape closes it and returns focus to the trigger.
Family
Actions
Things a person presses, and the chrome that gathers them.
8 documented entries in this family. Open the family index
Related