FAB
The floating trigger that expands into a small stack of actions — the primary "create" affordance in app-shaped mobile layouts.
The preview sits in a position: relative frame so the fixed button is contained
in the box — in your app you just drop in <x-wirekit::fab> and it pins to the
viewport corner (see the Show Code above, which omits the demo frame).
Usage
<x-wirekit::fab label="Create">
<x-wirekit::fab.action label="New note" icon="pencil" />
<x-wirekit::fab.action label="Upload file" icon="arrow-up-tray" />
<x-wirekit::fab.action label="Docs" icon="book-open" href="/docs" />
</x-wirekit::fab>
Every action needs a label. The buttons are icons, so the label is their whole
name — without one a screen reader announces "button" three times.
An action with an href renders as a link, and one without renders as a button. A
thing that navigates is a link; a thing that acts is a button. Getting that wrong
costs middle-click, "open in new tab", and the status bar.
Sitting above a bottom nav
Both are fixed, so a FAB on a page with a bottom nav
lands on top of it. Add wk-fab-above-nav:
<x-wirekit::fab label="Create" class="wk-fab-above-nav">…</x-wirekit::fab>
Opt-in, because this component cannot see whether a nav exists elsewhere in the
document — and guessing would lift every FAB off the corner it belongs in. The lift
disappears at md along with the nav; add wk-fab-above-nav-always if you kept
the nav on desktop.
Position
position follows the writing direction rather than assuming everyone reads left
to right:
position |
Where |
|---|---|
end |
The inline-end corner (default — bottom right in English) |
start |
The inline-start corner |
center |
Centered along the bottom |
Props
<x-wirekit::fab>
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
'Actions' |
Names the trigger and the menu. The trigger is an icon, so this is all a screen reader gets. |
position |
string |
'end' |
end, start, center. |
scope |
string|null |
null |
Class-scope override. |
<x-wirekit::fab.action>
| Prop | Type | Default | Description |
|---|---|---|---|
label |
string |
'' |
The action's whole accessible name. |
icon |
string|null |
null |
Icon name. Falls back to the slot. |
href |
string|null |
null |
Renders a link instead of a button. |
scope |
string|null |
null |
Class-scope override. |
Slots
| Slot | Description |
|---|---|
trigger |
Replaces the default plus icon. |
| default | The actions. |
Accessibility
The interesting part of a speed dial is not the fan — it is what happens to focus. A menu that opens and leaves focus on the trigger is a menu a keyboard user cannot reach; one that traps focus is a menu they cannot leave. This does neither:
- Opening moves focus to the first action.
- ↑ / ↓ walk the actions and wrap.
- Esc closes and hands focus back to the trigger — where the reader was — but only if focus was inside the menu. A stray Escape from elsewhere on the page does not yank it.
- The trigger's
aria-haspopup="menu"and the panel'srole="menu"agree, so a screen reader is not promised a popup whose contents are something else. - Actions are 44px, the touch minimum, and are the smallest targets on the screen.
- The plus/close icons both stay in the DOM so they can cross-fade, so the inactive one is hidden from assistive tech — otherwise the trigger announces both states.
- The rotation is dropped under
prefers-reduced-motion.
Keyboard Interaction
| Key | Action |
|---|---|
| Tab | Move focus to the trigger. |
| Enter / Space | Open the actions and focus the first. |
| ↑ ↓ | Move between actions. |
| Esc | Close and return focus to the trigger. |