Basic Floating Action Button
A round, fixed icon button for the one action a screen is really about - with a ring that survives any backdrop.
6 frameworksBeginner
A FAB that opens a vertical list of labelled actions, each row naming itself, with Escape and focus return wired up.
<!--
Each row carries its own VISIBLE label, so the label is the accessible name -
no aria-label per row to keep in sync. The disclosure contract is the same as
a speed dial: aria-expanded on the trigger, aria-controls at the list.
-->
<div class="fab-list group fixed bottom-6 right-6 z-40 flex flex-col items-end gap-3" data-open="false">
<ul id="fab-list-actions" class="flex flex-col items-end gap-2 group-data-[open=false]:hidden">
<li><button type="button" class="inline-flex h-11 items-center gap-2 rounded-full border border-gray-200 bg-white pl-3 pr-4 text-sm font-medium text-gray-700 shadow-[0_8px_20px_-6px_rgba(15,23,42,0.35)] hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-1 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950"><svg class="h-5 w-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M4 4h16v12l-4 4H4z" /></svg>New note</button></li>
<li><button type="button" class="inline-flex h-11 items-center gap-2 rounded-full border border-gray-200 bg-white pl-3 pr-4 text-sm font-medium text-gray-700 shadow-[0_8px_20px_-6px_rgba(15,23,42,0.35)] hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-1 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950"><svg class="h-5 w-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M12 19V5M5 12l7-7 7 7" /></svg>Upload file</button></li>
<li><button type="button" class="inline-flex h-11 items-center gap-2 rounded-full border border-gray-200 bg-white pl-3 pr-4 text-sm font-medium text-gray-700 shadow-[0_8px_20px_-6px_rgba(15,23,42,0.35)] hover:bg-gray-100 hover:text-gray-900 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white motion-safe:animate-in motion-safe:fade-in motion-safe:slide-in-from-bottom-1 dark:border-gray-800 dark:bg-gray-900 dark:text-gray-300 dark:hover:bg-gray-800 dark:hover:text-gray-50 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950"><svg class="h-5 w-5 shrink-0" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M16 19v-2a4 4 0 0 0-8 0v2M12 11a3 3 0 1 0 0-6 3 3 0 0 0 0 6" /></svg>Invite teammate</button></li>
</ul>
<button type="button" aria-expanded="false" aria-controls="fab-list-actions" aria-label="Open quick actions" class="inline-flex h-14 w-14 items-center justify-center rounded-full bg-blue-600 text-white shadow-[0_12px_28px_-8px_rgba(15,23,42,0.45)] hover:bg-blue-700 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 focus-visible:ring-offset-2 focus-visible:ring-offset-white dark:bg-blue-500 dark:hover:bg-blue-400 dark:focus-visible:ring-blue-400 dark:focus-visible:ring-offset-gray-950">
<svg class="h-6 w-6 transition-transform group-data-[open=true]:rotate-45 motion-reduce:transition-none" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14" /></svg>
</button>
</div>
<script>
document.querySelectorAll('.fab-list').forEach(function (root) {
var trigger = root.querySelector('[aria-controls="fab-list-actions"]');
trigger.addEventListener('click', function () {
var open = root.dataset.open !== 'true';
root.dataset.open = String(open);
trigger.setAttribute('aria-expanded', String(open));
trigger.setAttribute('aria-label', open ? 'Close quick actions' : 'Open quick actions');
});
});
</script>| Prop | Type | Default | Description |
|---|---|---|---|
items | ActionItem[] | - | The array of items to render. |
ariaLabel | string | 'Quick actions' | The button's accessible name. Required for icon-only buttons. |
onSelect | (id: string) => void | - | Fired with the menu item the user chose. |
Each row carries visible text, so the label is the accessible name - no per-row `aria-label` to drift. The disclosure contract is `aria-expanded` plus `aria-controls`; Escape closes it and closing returns focus to the trigger the actions came from.