Speed Dial / FAB
ProFloating action button with up/down/left/right expand directions, a custom action list, pulse animation, tooltip labels and viewport-aware positioning that auto-flips and clamps so the menu never leaves the screen.
import { SvSpeedDialComponent } from '@styloviz-pro/layout';Demo & usage
Basic usage
ProUsage example — open the gallery for the full interactive demo.
An interactive demo for this component lives in the gallery.
Open live demoMore examples
Left expand with tooltips
ProAlways-labeled variant
ProPinned to a corner, clear of other floating UI
Profloating pins the dial and joins the shared floating layer, so Scroll to Top, a host chat launcher, a cookie banner or a bottom nav in the same corner stack instead of overlapping.
Destructive action with confirmation
ProThe first activation arms the item and announces the prompt; the second confirms; Escape or moving away cancels. No window.confirm.
When to use
- Exposing a primary action plus a few related quick actions in a floating button.
- Mobile-first or content-heavy screens where a toolbar would crowd the layout.
- For a row of always-visible actions, use the Toolbar.
Async states
- error
Item identity & owned state
- Restorable state
- busyIds
Bind these to put a saved view back — the open sections, the hidden columns, the selected rows — and read their
Changeevents to store what the user does.
Inputs
| Name | Type | Default | Description |
|---|---|---|---|
| actionsrequired | readonly SpeedDialAction[] | — | The quick actions revealed when the dial opens. |
| icon | string | 'plus' | Icon-library name for the closed (trigger) state. |
| closeIcon | string | 'close' | Icon-library name for the open state. |
| direction | SpeedDialDirection | 'up' | Direction the actions fan out: up, down, left or right. |
| variant | SpeedDialVariant | 'default' | Visual variant. |
| label | string | 'Actions' | Accessible label for the trigger. |
| color | string | 'primary' | Trigger color (semantic token or CSS color). |
| tooltip | boolean | true | Show tooltips next to each action. |
| floating | SpeedDialFloatingCorner | null | null | Pin the dial `position: fixed` to this screen corner and join the shared floating layer, so it stacks clear of Scroll to Top, a host's chat launcher, cookie banner or bottom nav, and the device safe area. `null` leaves positioning to the host exactly as before. ⚠️ **To coordinate with Scroll to Top etc., set `floating`.** With the default `null` the dial is host-positioned — wherever the host's own CSS puts it — so it cannot know which corner it occupies and does NOT register with the floating layer. A Scroll to Top in the same corner will then sit on top of it. This stays opt-in so an existing host's layout is not moved under it. |
| floatingOrder | number | SV_FLOATING_ORDER.fab | Rank in the floating layer's corner stack; lower sits nearer the edge. Only read while `floating` is set. |
| confirmText | string | 'Confirm' | Word that prefixes an action's label while it waits for confirmation — the armed item is named "`confirmText`: `label`". Localise it here. Only used for actions with `destructive` / `confirm` and no custom prompt. |
| busyIds | readonly string[] | ReadonlySet<string> | [] | Ids of actions with a request in flight. Those controls disable and show a busy state, so a double-click cannot fire the same request twice — the most common way an action button creates duplicate records. |
| error | SvDataError | string | null | null | Failure from the last action. Rendered above the control group; nothing is removed from view, so the user can see what state things are in. |
Outputs
| Event | Payload type | Description |
|---|---|---|
| actionClick | string | Emitted with the id of the clicked action. |
Customization & theming
- Four expand directions.
- Three action styles: default, labeled and mini.
- Per-action colour, badge and disabled state.
- Solid, high-contrast label chips that stay legible while the page scrolls.
- Viewport-aware positioning: measures the panel, flips to the side with room, then shifts it fully on-screen.
- Floating layer (`floating`, `floatingOrder`): pins the dial to a corner with `position: fixed`, adds the device safe-area inset, and stacks it with every other registered region — Scroll to Top, affixed blocks, and anything the host registers through `SvFloatingLayerService` (chat launcher, cookie banner, bottom nav). A full-width edge region pushes every corner on its side; `floatingOrder` (default 10) decides who sits nearest the edge. `null` (default) leaves positioning to you.
- Confirmation: `destructive` (danger styling + confirmation) and `confirm` (`true` or a custom prompt string, `false` to opt out) per action; `confirmText` localises the default "Confirm: <label>" prompt.
- Variant axes: `variant` (default, labeled, mini) and `color` are appearance; `direction`, `tooltip`, `floating` and per-action `confirm` are behavioural modes. Every appearance works with every mode; `mini` with `tooltip=false` leaves only icons, so keep labels meaningful for the accessible name.
- Portals and layers: the menu is portalled to `<body>` through the kit's portal host, which carries a locally scoped `.dark`, `data-theme`, `dir` and `lang` from where the dial sits, and takes its z-index from the shared overlay stack — above page chrome, below a dialog opened later. The dial itself, when `floating`, sits in the floating band (z 50), always under overlays. It is not portalled, though: with `floating` set it is `position: fixed`, so an ancestor with `transform`, `filter`, `perspective`, `backdrop-filter`, `contain` or `container-type` becomes its containing block and it pins to that box, not the viewport — mount a floating dial outside such wrappers.
- Reduced motion: the pulse ring is removed, and the entrance stagger, icon turn and hover scale are stilled under `prefers-reduced-motion`. Forced colors: the pulse is hidden and the trigger and items get a ButtonText edge, plus a system-coloured focus outline (the focus ring is a box shadow, which forced colors removes).
- Browser limits: safe-area insets only apply when the page sets `viewport-fit=cover`; iOS Safari's virtual keyboard does not resize the layout viewport, so a fixed dial can sit under the keyboard while an input is focused — hide it (`@if`) while editing if that matters.
Best practices
- Do: Keep the action count small (3–5) so the fan stays tappable.
- Do: Label each action with a tooltip since the buttons are icon-only.
- Tip: The menu auto-flips to the opposite side and clamps to the viewport near edges — still set a sensible default `direction` for where the FAB usually sits.
- Do: Mark delete, archive and revoke actions `destructive` — on a phone a floating menu is one mis-tap from data loss, and the two-step confirmation is keyboard- and screen-reader-operable.
- Do: Use `floating` instead of hand-positioning the dial with `fixed bottom-6 right-6` when anything else can occupy that corner — only registered regions can be stacked around.
- Tip: Floating layer (X-5): the default dial (`floating` unset) is HOST-POSITIONED — it cannot know which corner your CSS put it in, so it registers nothing, and Scroll to Top or a chat launcher in the same corner will overlap it. To coordinate with them, set `floating` to its corner.
- Tip: Wrap action literals in `svSpeedDialAction()` to make an empty `label` a compile error. The field stays a plain `string` so data-driven actions keep compiling; a blank label that reaches the component logs a dev-mode warning instead.
- Don't: Don't grow past about five actions — beyond that it is a floating menu, and a Toolbar or a menu button serves it better.
Accessibility
- Trigger exposes aria-haspopup/aria-expanded/aria-controls; actions live in a labelled `role="menu"`.
- Focus moves into the menu on open and returns to the trigger on Escape/select; Arrow keys, Home and End navigate items.
- A confirming item keeps focus, is renamed to its prompt, and the prompt is announced assertively. Enter confirms, Escape cancels (a second Escape closes the menu), arrowing away cancels.
- Escape is owned by the topmost overlay, so a dialog opened over the dial closes first.
Feedback on Speed Dial / FAB
Spotted a gap in this page, or something that didn't work as described? Comments are GitHub Discussions in the public styloviz/community repo, so posting needs a GitHub account.