Floating Action Button
Mark any button nds-fab and give it an edge with data-fab-pos. It lifts out of the document to a fixed dock on that edge; buttons sharing an edge stack automatically, ordered by data-fab-order. No dock element to author.
<!-- data-fab-order stacks FABs sharing an edge: lower sits closer to it -->
<button class="nds-btn nds-fab nds-primary nds-circle nds-icon-only" type="button"
data-fab-pos="end" data-fab-order="0" data-panel-toggle="details" aria-label="Open panel" hidden>
<i class="hgi hgi-stroke hgi-menu-01"></i>
</button>
<!-- Add nds-fab-thumb above and the FAB rides this panel out as it opens -->
<aside id="details" class="nds-panel" data-panel-side="end" aria-label="Details" hidden>…</aside>
// Route a FAB built at runtime — what the demo button above does
const fab = document.createElement('button');
fab.className = 'nds-btn nds-fab nds-primary nds-circle nds-icon-only';
fab.setAttribute('aria-label', 'Open panel');
fab.innerHTML = '<i class="hgi hgi-stroke hgi-menu-01"></i>';
NDS.Fab.register(fab, 'end'); // 'left' | 'right' | 'bottom' | 'start' | 'end' | 'auto'
Grouped FAB
A FAB can be a container, not only a button. Mark an nds-btn-group and its actions travel and stack as a single dock item, at the buttons' own size and variant — add nds-vertical to run it down the edge instead of across.
<!-- The toggle lives on a child; with nds-fab-thumb the whole group rides -->
<div class="nds-fab nds-btn-group nds-vertical" data-fab-pos="end" data-fab-order="0" hidden>
<button class="nds-btn nds-subtle nds-icon-only" type="button"
data-panel-toggle="share-panel" aria-label="Open panel">
<i class="hgi hgi-stroke hgi-menu-01"></i>
</button>
<button class="nds-btn nds-subtle nds-icon-only" type="button" aria-label="Copy link">
<i class="hgi hgi-stroke hgi-link-01"></i>
</button>
<button class="nds-btn nds-subtle nds-icon-only" type="button" aria-label="Email">
<i class="hgi hgi-stroke hgi-mail-01"></i>
</button>
</div>
<aside id="share-panel" class="nds-panel" data-panel-side="end" aria-label="Details" hidden>…</aside>
// register() takes a CONTAINER as readily as a button — the whole
// group routes as one dock item, exactly like the demo above
const fab = document.createElement('div');
fab.className = 'nds-fab nds-btn-group nds-vertical';
fab.dataset.fabOrder = '0'; // lower sits closer to the edge when FABs share one
fab.innerHTML = `
<button class="nds-btn nds-subtle nds-icon-only" type="button" aria-label="Share">
<i class="hgi hgi-stroke hgi-share-01"></i>
</button>
<button class="nds-btn nds-subtle nds-icon-only" type="button" aria-label="Email">
<i class="hgi hgi-stroke hgi-mail-01"></i>
</button>`;
NDS.Fab.register(fab, 'end'); // 'left' | 'right' | 'bottom' | 'start' | 'end' | 'auto'
Built-in Features
Every .nds-fab is routed to its edge on load. Nothing to call, no dock element to author.
FABs resolve to one of the fixed edge docks — left, right, or bottom centre — so logical and physical positions never overlap.
Logical start and end resolve by text direction, and a runtime direction flip re-routes them; physical left and right stay put.
An auto FAB lands on the edge of the panel it toggles, and an edge thumb slides aside as that panel arrives rather than being buried by it.
FABs sharing an edge stack in a column, ordered by data-fab-order, with the lowest sitting closest to the edge.
Near the bottom of a scrollable page each dock slides out to its edge so a fixed FAB never covers footer content.
Circles, edge thumbs, and multi-button groups share an edge without being resized; each keeps its own size and shape.
Register a FAB built at runtime, or by another component, straight into its edge dock through the JS API.
Usage Guidelines
Best Practices
- Use a FAB for one primary or persistent action that should stay reachable as the user scrolls: compose, add, open filters, or start a chat
- Keep FABs few. An edge crowded with actions loses the "primary action" meaning; move secondary actions into a Panel the FAB opens
- Do not use a FAB for an action tied to a specific place in the content. Put an inline Button next to what it acts on instead
- Prefer logical
startandend(orauto) so the edge follows reading direction. Reserveleftandrightfor an action that must stay on the same physical edge in any language - Give a FAB that opens a panel
data-fab-pos="auto"so it sits on the same edge the panel slides from - Order a stack with
data-fab-order: the lowest number sits closest to the edge. Put the primary action lowest - Group related actions (a share cluster, for example) in a single
nds-btn-group nds-verticalso they travel and stack as one item. It is the ordinary button group — do not restyle it for the edge; pick the button variant and size you want and let the group be - Add
nds-fab-thumbto a FAB — a button or a whole group — that should sit against the viewport edge. Do not hand-write the squared corners; the dock the FAB lands in decides them, and a hard-coded radius breaks the moment the FAB moves edge - Ship each FAB with the
hiddenattribute so it never flashes at its authored spot before routing lifts it to the edge
Modifier Classes
| Class | Description |
|---|---|
nds-fab | Marks an element (a button or a container) as a FAB. Routing moves it into its edge dock, and it gains a shadow so it reads as detached from the page. On a button it also sets the footprint — 16px larger than the same button inline (8px on small screens), so every size class still applies: LG 56, MD 48, SM 40. It also gives nds-secondary-outline and nds-subtle an opaque fill so page content cannot read through them |
nds-fab-thumb | Turns a FAB into an edge thumb: it gives up the dock's inset and sits against the viewport edge. The corners on that edge are squared off automatically, picked from the edge the FAB docks on — a logical start/end FAB gets the right side in both directions. The bottom dock has no side edge, so it meets the floor instead. A thumb also rides the Panel it toggles: when that panel slides from the same edge, the thumb travels aside with it instead of being buried, and returns as it closes |
nds-btn-group | Not a FAB class — the Button group. Mark one nds-fab and several actions travel and stack as a single dock item, at the buttons' own size and variant. Add nds-vertical to stack it down an edge instead of across |
Data Attributes
| Attribute | Description |
|---|---|
data-fab-pos | Set on a .nds-fab to pick its edge. Values: left, right, bottom (physical, fixed); start, end (logical, resolve by direction); auto (default, follow the toggled panel's side) |
data-fab-order | Set on a .nds-fab to order the stack on its edge. Lower numbers sit closer to the edge. Default 0 |
data-panel-toggle | Set on a FAB to open a Panel by id. With data-fab-pos="auto", the FAB follows that panel to its edge. With an explicit data-fab-pos the two are independent — the thumb only rides when both resolve to the same edge, so keep them in sync or use auto. On an nds-fab-thumb it also drives the ride: set it on the toggle inside a grouped FAB and the whole group travels |
data-fab-dock-pos | Set on a hand-authored .nds-fab-dock to fix its edge. Docks are usually created automatically, so this is rarely needed |
CSS Custom Properties
| Property | Default | Description |
|---|---|---|
--fab-dock-offset | calc(--nds-viewport-padding / 2) | Distance from the viewport edges to the dock. Half the page gutter, so it narrows with the page on mobile |
--fab-dock-gap | --spacing-md | Gap between stacked FABs on an edge |
--fab-dock-z | 899 | Stacking order of the docks, below modals and backdrops |
JavaScript API
Authored .nds-fab elements route automatically, so most pages never call this API. Use NDS.Fab.register for a FAB built at runtime or injected by another component.