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 whose actions fan out along a quarter arc, using the disclosure pattern with focusable-only-when-open actions.
<!--
The actions fan out along a quarter arc. They stay in the DOM but toggle
`invisible` (visibility, not opacity) - a collapsed action is removed from the
tab order, so a keyboard user never lands on a control they cannot see.
-->
<div class="fab-radial group fixed bottom-6 right-6 z-40 h-14 w-14" data-open="false">
<div id="fab-radial-actions" class="contents">
<button type="button" aria-label="New note" class="invisible absolute bottom-1.5 right-1.5 inline-flex h-11 w-11 scale-50 items-center justify-center rounded-full border border-gray-200 bg-white text-gray-700 opacity-0 shadow-[0_8px_20px_-6px_rgba(15,23,42,0.35)] transition-all duration-200 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 group-data-[open=true]:visible group-data-[open=true]:-translate-y-[4.75rem] group-data-[open=true]:scale-100 group-data-[open=true]:opacity-100 motion-reduce:transition-none 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" 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>
</button>
<button type="button" aria-label="Upload file" class="invisible absolute bottom-1.5 right-1.5 inline-flex h-11 w-11 scale-50 items-center justify-center rounded-full border border-gray-200 bg-white text-gray-700 opacity-0 shadow-[0_8px_20px_-6px_rgba(15,23,42,0.35)] transition-all duration-200 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 group-data-[open=true]:visible group-data-[open=true]:-translate-x-[3.375rem] group-data-[open=true]:-translate-y-[3.375rem] group-data-[open=true]:scale-100 group-data-[open=true]:opacity-100 motion-reduce:transition-none 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" 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>
</button>
<button type="button" aria-label="Invite teammate" class="invisible absolute bottom-1.5 right-1.5 inline-flex h-11 w-11 scale-50 items-center justify-center rounded-full border border-gray-200 bg-white text-gray-700 opacity-0 shadow-[0_8px_20px_-6px_rgba(15,23,42,0.35)] transition-all duration-200 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 group-data-[open=true]:visible group-data-[open=true]:-translate-x-[4.75rem] group-data-[open=true]:scale-100 group-data-[open=true]:opacity-100 motion-reduce:transition-none 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" 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>
</button>
</div>
<button type="button" aria-expanded="false" aria-controls="fab-radial-actions" aria-label="Open actions" class="relative z-10 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-radial').forEach(function (root) {
var trigger = root.querySelector('[aria-controls="fab-radial-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 actions' : 'Open actions');
});
});
</script>| Prop | Type | Default | Description |
|---|---|---|---|
items | RadialAction[] | - | The array of items to render. |
onSelect | (id: string) => void | - | Fired with the menu item the user chose. |
Actions stay mounted and toggle `invisible` (visibility, not just opacity) so a collapsed action leaves the tab order - a transparent-but-focusable control is the bug this avoids. Each action carries its own `aria-label`, the arc positions are per-item translate utilities, and the whole expansion sits behind `motion-reduce`.