Skip to content
Overlays

FloatingAction

The round launcher pinned to the bottom corner of the viewport, on its own. CornerPanel already parks one of these in that corner to open a panel; this is the same 56px object when what it opens is a menu, a dialog, or nothing at all.

Import

import { FloatingAction } from "@mikenotthepope/substrateui"

It holds no menu, and that is the point

A launcher with a menu built into it would be a second menu in this library, with its own idea of what an arrow key does. DropdownMenu already owns that job, and owns it better than a hand-rolled copy: the highlight wraps, Escape closes, Tab out closes, and a press that focuses nothing does not.

So the composition is the API. Hand the launcher to DropdownMenuTrigger through its renderprop and the two halves stay each other's business.

positionMethod="fixed" is not decoration

A launcher pinned with position: fixed is anchored in viewport coordinates. A popup placed absolute is measured in document ones. The two agree only while the page is scrolled to the top, so on a long page the menu parts company with the button the moment the reader scrolls.

DropdownMenuContent takes positionMethod for exactly this. side="top" align="end" is the other half: a menu over a bottom corner opens upward and toward the corner it came from, and Base UI moves it back inside the viewport on its own if there is no room.

Sharing the corner

The corner is a single resource and this library now puts two things in it. lift is how the second one gets out of the first one's way: lift={1} is one launcher up, lift={2}two. The arithmetic is 56 pixels of button and 16 of air, so each step is 72 on top of the corner's own 20.

Both specimens on this page pass absolute in className to scope themselves to the dashed box. Three viewport-fixed launchers piled into one corner would show one component and lie about two.

end-5 rather than right-5

The corner follows the reading direction, so the launcher moves to the other side under DirectionProvider with no second rule. The menu follows it: align="end" is a logical alignment too.

Accessibility

It is icon-only, so it needs an accessible name and nothing here can guess it. Give it an aria-label. As a DropdownMenuTrigger it also picks up aria-haspopup="menu", aria-expanded and aria-controls from Base UI, so the name is the only part left to you.

It renders type="button", so a launcher that happens to sit inside a form cannot submit it.

One thing worth knowing about the menu it opens, because a hand-rolled copy usually gets it wrong: Safari does not focus a button when you tap it, so the press lands on a menu item while focus falls to the document body. A menu that closes on any focus-out unmounts the item before its click arrives, and the tap does nothing at all. Base UI closes on focus moving somewhere else, which a Tab does and a tap does not, so the press lands.

API Reference

PropTypeDefaultDescription
lift0 | 1 | 20How many launchers already hold the corner. `0` is the corner itself, `1` sits one launcher above it, `2` two.
variantButton variant"default"Passed straight to `Button`. The size is fixed at 56px square.
renderReactElement—Render a different element instead of a button, the way `Button` does.