18 / 20
All 20 in 20 Tailwind Liquid Glass Effects
- 01Tailwind Liquid Glass Card with Hover Refraction
- 02Tailwind Liquid Glass Button with Glossy Highlight
- 03Tailwind Liquid Glass Navigation Bar
- 04Tailwind Liquid Gooey Toggle Switch
- 05Tailwind Liquid Glass Modal Dialog
- 06Tailwind Liquid Glass Theme Switcher
- 07Tailwind Liquid Glass Input Form Fields
- 08Tailwind SVG Displacement Liquid Glass Background
- 09Tailwind Liquid Glass Dropdown Menu
- 10Tailwind Liquid Glass Pricing Table
- 11Tailwind Liquid Glass Tooltip Popover
- 12Tailwind Liquid Glass Audio Player UI
- 13Tailwind Liquid Glass Range Slider
- 14Tailwind Liquid Glass Toast Notification
- 15Tailwind Liquid Glass Sidebar Navigation
- 16Tailwind Liquid Glass Accordion
- 17Tailwind Liquid Glass Login Card
- 18Tailwind Liquid Glass Floating Action Button
- 19Tailwind Liquid Glass Loading Skeleton
- 20Tailwind Liquid Glass Multi-Step Form Wizard
Tailwind Liquid Glass Floating Action Button
A glass FAB that fans out into frosted sub-actions on tap, staggered like droplets. All of it is one sr-only checkbox and peer-checked: transforms — and because each sub-action carries its own delay utility, the stagger is readable in the markup instead of computed in a stylesheet.
Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time
Untitled UI React · Affiliate link
The code
<section class="lgt-18 relative w-full min-h-svh overflow-hidden bg-lgt18-bg font-lgt18-sans" aria-label="Tailwind liquid glass floating action button">
<img src="https://picsum.photos/seed/lgfab/1000/750" width="1000" height="750" alt="App backdrop" class="absolute inset-0 size-full object-cover">
<div class="absolute right-8 bottom-8 size-15.5">
<input type="checkbox" id="lgt-18-open" class="peer sr-only">
<a href="#lgt-18" aria-label="Share" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-0 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-y-[74px] motion-reduce:transition-opacity motion-reduce:peer-checked:translate-y-0 motion-reduce:peer-checked:-translate-y-[74px]">↗</a>
<a href="#lgt-18" aria-label="Add photo" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-75 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[52px] peer-checked:-translate-y-[52px] motion-reduce:transition-opacity">◨</a>
<a href="#lgt-18" aria-label="New note" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-150 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[74px] motion-reduce:transition-opacity">✎</a>
<label for="lgt-18-open" aria-label="Open actions" class="absolute inset-0 z-3 grid cursor-pointer place-items-center rounded-full border border-lgt18-accent/60 bg-linear-to-b from-lgt18-accent-top to-lgt18-accent shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_16px_30px_-12px_rgba(0,0,0,.7)] transition-transform duration-300 peer-checked:rotate-135 peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-white motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-5.5 before:absolute before:top-1/2 before:left-1/2 before:h-[2.5px] before:w-5 before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-sm before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[2.5px] after:w-5 after:-translate-x-1/2 after:-translate-y-1/2 after:rotate-90 after:rounded-sm after:bg-white"></span>
</label>
</div>
</section><section class="lgt-18 relative w-full min-h-svh overflow-hidden bg-lgt18-bg font-lgt18-sans" aria-label="Tailwind liquid glass floating action button">
<img src="https://picsum.photos/seed/lgfab/1000/750" width="1000" height="750" alt="App backdrop" class="absolute inset-0 size-full object-cover">
<div class="absolute right-8 bottom-8 size-15.5">
<input type="checkbox" id="lgt-18-open" class="peer sr-only">
<a href="#lgt-18" aria-label="Share" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-0 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-y-[74px] motion-reduce:transition-opacity motion-reduce:peer-checked:translate-y-0 motion-reduce:peer-checked:-translate-y-[74px]">↗</a>
<a href="#lgt-18" aria-label="Add photo" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-75 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[52px] peer-checked:-translate-y-[52px] motion-reduce:transition-opacity">◨</a>
<a href="#lgt-18" aria-label="New note" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-150 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[74px] motion-reduce:transition-opacity">✎</a>
<label for="lgt-18-open" aria-label="Open actions" class="absolute inset-0 z-3 grid cursor-pointer place-items-center rounded-full border border-lgt18-accent/60 bg-linear-to-b from-lgt18-accent-top to-lgt18-accent shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_16px_30px_-12px_rgba(0,0,0,.7)] transition-transform duration-300 peer-checked:rotate-135 peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-white motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-5.5 before:absolute before:top-1/2 before:left-1/2 before:h-[2.5px] before:w-5 before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-sm before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[2.5px] after:w-5 after:-translate-x-1/2 after:-translate-y-1/2 after:rotate-90 after:rounded-sm after:bg-white"></span>
</label>
</div>
</section>@import "tailwindcss";
@theme {
--color-lgt18-bg: #100c1e;
--color-lgt18-accent: #6d4cf2;
--color-lgt18-accent: oklch(0.64 0.2 285);
--color-lgt18-accent-top: #8a6ff7;
--color-lgt18-accent-top: oklch(0.7 0.17 285);
--font-lgt18-sans: 'Manrope', system-ui, sans-serif;
--ease-lgt18: cubic-bezier(0.3, 1.3, 0.5, 1);
}@import "tailwindcss";
@theme {
--color-lgt18-bg: #100c1e;
--color-lgt18-accent: #6d4cf2;
--color-lgt18-accent: oklch(0.64 0.2 285);
--color-lgt18-accent-top: #8a6ff7;
--color-lgt18-accent-top: oklch(0.7 0.17 285);
--font-lgt18-sans: 'Manrope', system-ui, sans-serif;
--ease-lgt18: cubic-bezier(0.3, 1.3, 0.5, 1);
}Setup — do this once per project
Skip this if your project already runs Tailwind v4.
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>
<script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script>Compiles in the browser — perfect for a CodePen or a quick test, not for production.
npm install tailwindcss @tailwindcss/vite
npm install tailwindcss @tailwindcss/vite// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})// vite.config.js ("type": "module" in package.json)
import { defineConfig } from 'vite'
import tailwindcss from '@tailwindcss/vite'
export default defineConfig({
plugins: [tailwindcss()],
})Any v4 setup works — Vite, PostCSS or the CLI. Nothing in this demo is bundler-specific.
@import "tailwindcss";
@theme {
--color-lgt18-bg: #100c1e;
--color-lgt18-accent: #6d4cf2;
--color-lgt18-accent: oklch(0.64 0.2 285);
--color-lgt18-accent-top: #8a6ff7;
--color-lgt18-accent-top: oklch(0.7 0.17 285);
--font-lgt18-sans: 'Manrope', system-ui, sans-serif;
--ease-lgt18: cubic-bezier(0.3, 1.3, 0.5, 1);
}@import "tailwindcss";
@theme {
--color-lgt18-bg: #100c1e;
--color-lgt18-accent: #6d4cf2;
--color-lgt18-accent: oklch(0.64 0.2 285);
--color-lgt18-accent-top: #8a6ff7;
--color-lgt18-accent-top: oklch(0.7 0.17 285);
--font-lgt18-sans: 'Manrope', system-ui, sans-serif;
--ease-lgt18: cubic-bezier(0.3, 1.3, 0.5, 1);
}<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin> <link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;800&display=swap">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Manrope:wght@400;600;800&display=swap">What changed in the port
Which parts became utilities, which stayed CSS, and why.
- The
--dcustom property and itscalc()delay disappear: each action carries its owndelay-*utility, so the stagger is readable at a glance. peer-checked:rotate-135turns the plus into a close X with no second icon and no state class.pointer-events-noneon the resting actions is required —scale-0elements still receive clicks otherwise.- The FAB's focus ring comes from
peer-focus-visible:, since the focusable element is the clipped checkbox and not the label. - Under
motion-reducethe transition narrows to opacity so the menu still appears, just without flying across the screen.
Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Liquid Glass Floating Action Button
Source: https://codefronts.com/design-styles/tailwind-liquid-glass-effects/tailwind-liquid-glass-floating-action-button/
A glass FAB that fans out into frosted sub-actions on tap, staggered like droplets. All of it is one sr-only checkbox and peer-checked: transforms — and because each sub-action carries its own delay utility, the stagger is readable in the markup instead of computed in a stylesheet.
## HTML
```html
<section class="lgt-18 relative w-full min-h-svh overflow-hidden bg-lgt18-bg font-lgt18-sans" aria-label="Tailwind liquid glass floating action button">
<img src="https://picsum.photos/seed/lgfab/1000/750" width="1000" height="750" alt="App backdrop" class="absolute inset-0 size-full object-cover">
<div class="absolute right-8 bottom-8 size-15.5">
<input type="checkbox" id="lgt-18-open" class="peer sr-only">
<a href="#lgt-18" aria-label="Share" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-0 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-y-[74px] motion-reduce:transition-opacity motion-reduce:peer-checked:translate-y-0 motion-reduce:peer-checked:-translate-y-[74px]">↗</a>
<a href="#lgt-18" aria-label="Add photo" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-75 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[52px] peer-checked:-translate-y-[52px] motion-reduce:transition-opacity">◨</a>
<a href="#lgt-18" aria-label="New note" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-150 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[74px] motion-reduce:transition-opacity">✎</a>
<label for="lgt-18-open" aria-label="Open actions" class="absolute inset-0 z-3 grid cursor-pointer place-items-center rounded-full border border-lgt18-accent/60 bg-linear-to-b from-lgt18-accent-top to-lgt18-accent shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_16px_30px_-12px_rgba(0,0,0,.7)] transition-transform duration-300 peer-checked:rotate-135 peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-white motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-5.5 before:absolute before:top-1/2 before:left-1/2 before:h-[2.5px] before:w-5 before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-sm before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[2.5px] after:w-5 after:-translate-x-1/2 after:-translate-y-1/2 after:rotate-90 after:rounded-sm after:bg-white"></span>
</label>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-lgt18-bg: #100c1e;
--color-lgt18-accent: #6d4cf2;
--color-lgt18-accent: oklch(0.64 0.2 285);
--color-lgt18-accent-top: #8a6ff7;
--color-lgt18-accent-top: oklch(0.7 0.17 285);
--font-lgt18-sans: 'Manrope', system-ui, sans-serif;
--ease-lgt18: cubic-bezier(0.3, 1.3, 0.5, 1);
}
```Here's a working CSS demo from CodeFronts. Use it as-is or adapt to your framework. All classes are scoped under a unique prefix so the code won't collide with your existing styles. MIT licensed.
Demo: Tailwind Liquid Glass Floating Action Button
Source: https://codefronts.com/design-styles/tailwind-liquid-glass-effects/tailwind-liquid-glass-floating-action-button/
A glass FAB that fans out into frosted sub-actions on tap, staggered like droplets. All of it is one sr-only checkbox and peer-checked: transforms — and because each sub-action carries its own delay utility, the stagger is readable in the markup instead of computed in a stylesheet.
## HTML
```html
<section class="lgt-18 relative w-full min-h-svh overflow-hidden bg-lgt18-bg font-lgt18-sans" aria-label="Tailwind liquid glass floating action button">
<img src="https://picsum.photos/seed/lgfab/1000/750" width="1000" height="750" alt="App backdrop" class="absolute inset-0 size-full object-cover">
<div class="absolute right-8 bottom-8 size-15.5">
<input type="checkbox" id="lgt-18-open" class="peer sr-only">
<a href="#lgt-18" aria-label="Share" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-0 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-y-[74px] motion-reduce:transition-opacity motion-reduce:peer-checked:translate-y-0 motion-reduce:peer-checked:-translate-y-[74px]">↗</a>
<a href="#lgt-18" aria-label="Add photo" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-75 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[52px] peer-checked:-translate-y-[52px] motion-reduce:transition-opacity">◨</a>
<a href="#lgt-18" aria-label="New note" class="pointer-events-none absolute right-2.75 bottom-2.75 z-2 grid size-11 scale-0 place-items-center rounded-full border border-white/40 bg-white/16 text-[17px] text-white no-underline opacity-0 backdrop-blur-[14px] backdrop-saturate-170 shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_10px_22px_-10px_rgba(0,0,0,.6)] transition-[transform,opacity] duration-400 ease-lgt18 delay-150 focus-visible:outline-3 focus-visible:outline-offset-3 focus-visible:outline-white peer-checked:pointer-events-auto peer-checked:scale-100 peer-checked:opacity-100 peer-checked:-translate-x-[74px] motion-reduce:transition-opacity">✎</a>
<label for="lgt-18-open" aria-label="Open actions" class="absolute inset-0 z-3 grid cursor-pointer place-items-center rounded-full border border-lgt18-accent/60 bg-linear-to-b from-lgt18-accent-top to-lgt18-accent shadow-[inset_0_1px_0_rgba(255,255,255,.5),0_16px_30px_-12px_rgba(0,0,0,.7)] transition-transform duration-300 peer-checked:rotate-135 peer-focus-visible:outline-3 peer-focus-visible:outline-offset-3 peer-focus-visible:outline-white motion-reduce:transition-none">
<span aria-hidden="true" class="relative size-5.5 before:absolute before:top-1/2 before:left-1/2 before:h-[2.5px] before:w-5 before:-translate-x-1/2 before:-translate-y-1/2 before:rounded-sm before:bg-white after:absolute after:top-1/2 after:left-1/2 after:h-[2.5px] after:w-5 after:-translate-x-1/2 after:-translate-y-1/2 after:rotate-90 after:rounded-sm after:bg-white"></span>
</label>
</div>
</section>
```
## CSS
```css
@import "tailwindcss";
@theme {
--color-lgt18-bg: #100c1e;
--color-lgt18-accent: #6d4cf2;
--color-lgt18-accent: oklch(0.64 0.2 285);
--color-lgt18-accent-top: #8a6ff7;
--color-lgt18-accent-top: oklch(0.7 0.17 285);
--font-lgt18-sans: 'Manrope', system-ui, sans-serif;
--ease-lgt18: cubic-bezier(0.3, 1.3, 0.5, 1);
}
```How this works
The checkbox is the menu state. Clipped with sr-only so it stays keyboard-reachable, it sits before the actions in source order — which is what makes peer-checked: able to reach them:
<input type="checkbox" class="peer sr-only">
<a class="scale-0 opacity-0 delay-75
peer-checked:scale-100 peer-checked:opacity-100
peer-checked:-translate-y-[74px]">The stagger lives on the elements. Three actions, three delay utilities. In the CSS version this was a calc(var(--d) * 40ms) expression you had to decode; here the timing of each droplet is the number typed next to it.
Fan-out is pure transform. Each action rests at scale-0 in the FAB's centre and travels to its position with a springy cubic-bezier. Nothing animates layout, so three simultaneous droplets cost one composited frame each.
The FAB label is the visible control. Its focus ring comes from peer-focus-visible:, because the thing that actually receives focus is invisible — the same pattern as demo 13's lens.
Make it yours
- Change the fan geometry by editing each action's
peer-checked:translate-*pair; a quarter-circle, a vertical stack and a diagonal line are all one-utility changes. - Tighten or loosen the stagger with the delay utilities — 75ms apart reads as liquid, 200ms apart reads as a slow menu.
- Add a fourth action by copying a row and giving it a new angle and delay.
- Recolour through
--color-lgt18-accent; the sub-actions stay neutral glass on purpose so the primary reads as primary. - Turn it into a vertical speed dial by making all three translations vertical with increasing offsets.
Gotchas — read before shipping
- Keep the toggle a real checkbox. A div with a click handler loses keyboard operation, and a FAB is exactly the control a keyboard user needs to reach.
peer-*only reaches later siblings, so the input must come first in the DOM — a source-order bug here produces no error and no effect.- Animate transform and opacity, never
top/left. Three simultaneously animating layout properties is how a FAB starts dropping frames on mid-range phones. - Icon-only actions need
aria-labels. A fan of unlabelled glyphs is a dead end for screen-reader users. - The closed actions must be
pointer-events-noneor invisiblescale-0elements will still swallow clicks near the FAB. - Under
motion-reducekeep the opacity change and drop the travel — the menu must still appear, just without flying.
Browser support
| Chrome | Safari | Firefox | Edge |
|---|---|---|---|
| 111+ | 16.4+ | 128+ | 111+ |
Utilities only, no JavaScript — a checkbox, sibling variants and transforms. Where backdrop-filter is unavailable the actions are solid glass circles with identical behaviour.