Actions
FloatingIconButton
A round action pinned to a screen corner.
Usage
When to reach for it
import { FloatingIconButton } from '@misoto22/design'Notes
A round action pinned to a screen corner.
A translucent paper ground with a backdrop blur rather than a drop shadow — the White Reset has no elevation ramp, so what separates the control from the page under it is the blur and the hairline, not a glow.
Anatomy
| Element | Description |
|---|---|
| Control boxrequired | A real <button type="button">, --control-h-md square with a pill radius, position: fixed to a bottom corner at --z-drawer. There is no asChild and no href here: it cannot become a link. |
| Iconrequired | children, and the only thing in the box. Nothing text-shaped renders, so the control is as wide as the square and no wider. |
| Groundrequired | A 90% --paper fill over a backdrop blur, with a --rule-2 hairline. The White Reset has no elevation ramp, so the blur and the hairline are what lift it off the page — not a shadow. |
| Namerequired | label, set as aria-label. It never renders, so a screen reader has the name and a sighted reader has the glyph alone. |
Best practices
Do
- Spell out position — it is required and has no default, and the values are start and end in reading order. The component’s own doc example says position="right", which is not one of them and does not type-check.
- Wrap it in a Tooltip when the glyph is not universal: label is aria-label only, so it names the control for a screen reader and for nobody else — and a tooltip does not open on touch, which is where a floating control is most often the only affordance on screen.
- Keep it to two. start is deliberately raised to 5rem while end sits at 1.5rem, which is exactly enough clearance for a pair; a third has nowhere left to go, and the corners already compete with a cookie bar and a chat launcher.
- Move it with className when the page has a fixed footer — the classes go through tailwind-merge, so className="bottom-24" REPLACES the corner offset rather than losing to it.
Don’t
- --control-h-md is 44px at the default density and 36px under data-density="compact", so a compact page ships the one control a thumb reaches for without looking at eight pixels under the WCAG 2.5.5 floor.
- It is position: fixed, so any ancestor with a transform, filter or backdrop-filter becomes its containing block — put one inside a DialogContent, which centres itself with a translate, and it pins to the panel’s corner instead of the screen’s.
- It sits at --z-drawer, 100, and every rank that can appear over it is higher: a scrim is 200, a modal 210, an anchored panel 220 — --z-dropdown now resolves to --z-anchored, not to this rank. So a menu opening into the same corner covers the button outright rather than tying with it, and a dialog covers it too. Nothing here is settled by document order; move the button with className if it must stay reachable beside something else.
Examples
back to top
A page-level affordance that stays reachable while the reader scrolls. position is required and has no default, and its values are start and end in reading order — end is the right in English and the left in Arabic. label is set as aria-label and never renders, so a screen reader has the name and a sighted reader has the glyph alone: pick a glyph that carries the meaning, or wrap it in a Tooltip. The control is fixed to the viewport in real use; the box around it here is what keeps the preview honest, since a fixed element inside a transformed ancestor pins to that ancestor instead of the screen.
a tooltip name
label is aria-label and nothing else: it names the control for a screen reader and for nobody else, so a glyph that is not universal needs the word on screen too. A Tooltip is how, with one caveat that matters here more than anywhere — Radix returns early on a touch pointer, so the tip never opens on a phone, which is exactly where a floating control is most often the only affordance in view. The tip and the label are one string, so the two cannot drift apart (WCAG 2.5.3).
two corners
Both corners, and the reason there is no room for a third. start is raised to 5rem while end sits at 1.5rem, which is exactly the clearance a pair needs — and the corners are already competing with a cookie bar, a chat launcher and whatever the browser puts in the corner where reading begins, which is why start is the higher of the two. The control is --control-h-md square: 44px at the default density, which is the pointer-target floor, and 36px under data-density="compact". Flip the density switch above and the one control a thumb reaches for without looking drops eight pixels under it.
Accessibility
- label is the only name the control has; it is required rather than optional.
- 44px square, which is the pointer-target floor (WCAG 2.5.8).