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 modal bottom sheet of actions, with Escape to close and focus that moves in and returns.
<!--
The sheet is a modal dialog: aria-modal, an Escape close, and focus that moves
in on open and returns to the trigger on close (the trigger it came from). The
scrim and the sheet both fade/slide only under motion-safe.
-->
<button type="button" aria-haspopup="dialog" aria-expanded="false" aria-controls="fab-sheet" aria-label="Create new" class="fab-sheet-trigger fixed bottom-6 right-6 z-40 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" 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 id="fab-sheet" class="fixed inset-0 z-50 hidden">
<div class="fab-sheet-scrim absolute inset-0 bg-gray-900/40 motion-safe:animate-in motion-safe:fade-in"></div>
<div role="dialog" aria-modal="true" aria-label="Create new" class="absolute inset-x-0 bottom-0 rounded-t-2xl border-t border-gray-200 bg-white p-5 shadow-[0_-20px_40px_-12px_rgba(15,23,42,0.4)] motion-safe:animate-in motion-safe:slide-in-from-bottom dark:border-gray-800 dark:bg-gray-900">
<div class="mx-auto mb-4 h-1.5 w-10 rounded-full bg-gray-300 dark:bg-gray-700" aria-hidden="true"></div>
<h2 class="text-base font-semibold text-gray-900 dark:text-gray-50">Create new</h2>
<div class="mt-4 grid gap-2">
<button type="button" class="fab-sheet-item flex h-11 items-center gap-3 rounded-lg px-3 text-sm font-medium text-gray-700 hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:text-gray-300 dark:hover:bg-gray-800 dark:focus-visible:ring-blue-400">Document</button>
<button type="button" class="fab-sheet-item flex h-11 items-center gap-3 rounded-lg px-3 text-sm font-medium text-gray-700 hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:text-gray-300 dark:hover:bg-gray-800 dark:focus-visible:ring-blue-400">Spreadsheet</button>
<button type="button" class="fab-sheet-item flex h-11 items-center gap-3 rounded-lg px-3 text-sm font-medium text-gray-700 hover:bg-gray-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-blue-600 dark:text-gray-300 dark:hover:bg-gray-800 dark:focus-visible:ring-blue-400">Folder</button>
</div>
</div>
</div>
<script>
(function () {
var trigger = document.querySelector('.fab-sheet-trigger');
var sheet = document.getElementById('fab-sheet');
if (!trigger || !sheet) return;
var scrim = sheet.querySelector('.fab-sheet-scrim');
function open(state) {
sheet.classList.toggle('hidden', !state);
trigger.setAttribute('aria-expanded', String(state));
if (state) { var first = sheet.querySelector('.fab-sheet-item'); if (first) first.focus(); }
else trigger.focus();
}
trigger.addEventListener('click', function () { open(true); });
scrim.addEventListener('click', function () { open(false); });
document.addEventListener('keydown', function (e) { if (e.key === 'Escape' && !sheet.classList.contains('hidden')) open(false); });
sheet.querySelectorAll('.fab-sheet-item').forEach(function (item) { item.addEventListener('click', function () { open(false); }); });
})();
</script>| Prop | Type | Default | Description |
|---|---|---|---|
title | string | 'Create new' | Heading text for the card. |
items | SheetItem[] | - | The array of items to render. |
onSelect | (id: string) => void | - | Fired with the menu item the user chose. |
The sheet is a modal dialog: `aria-modal`, an Escape close, focus moved to the first action on open, and focus returned to the trigger on close. The scrim and the sheet slide and fade only under `motion-safe`.