Tailwind v4MIT licensed

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.

Live Demo
Plain CSSTailwind v4
Try it

Untitled UI Pro: 5,000+ React + Tailwind CSS v4 components · from $349 one-time

Untitled UI React · Affiliate link

Get Untitled UI Pro

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]">&#8599;</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">&#9704;</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">&#9998;</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);
}

Setup — do this once per project

Skip this if your project already runs Tailwind v4.

1Load Tailwind
<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.

2Add the stylesheetRequiredthe @theme block is what makes the custom utilities exist
@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);
}
3Fonts used by this demooptional — skip if you have your own type stack
<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 --d custom property and its calc() delay disappear: each action carries its own delay-* utility, so the stagger is readable at a glance.
  • peer-checked:rotate-135 turns the plus into a close X with no second icon and no state class.
  • pointer-events-none on the resting actions is required — scale-0 elements 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-reduce the transition narrows to opacity so the menu still appears, just without flying across the screen.
Paste this into ChatGPT, Claude, Cursor, or any coding assistant. The block below is pre-framed with everything the AI needs to integrate this demo into your project — markup, styles, scoping notes, and the source URL. Hit Copy and paste straight into your chat.
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 &mdash; 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]">&#8599;</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">&#9704;</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">&#9998;</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-none or invisible scale-0 elements will still swallow clicks near the FAB.
  • Under motion-reduce keep the opacity change and drop the travel — the menu must still appear, just without flying.

Browser support

ChromeSafariFirefoxEdge
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.

Techniques used in this demo

Theme tokensset in @theme, used by a utility

HTML & JSno Tailwind involved

Search CodeFronts

Loading…