34 CSS Floating Buttons
08 / 34

All 34 in 34 CSS Floating Buttons

  1. 01Material Design 3 Floating Action Button (FAB)
  2. 02iOS-Style Compose Floating Button
  3. 03WhatsApp Floating Message Button CSS
  4. 04Intercom / Drift Chat Bubble Floating Button
  5. 05Accessible WAI-ARIA Floating Action Button (WCAG 2.2)
  6. 06Extended FAB with Icon and Label (Material Design 3)
  7. 07Speed Dial Radial Menu FAB with Backdrop
  8. 08Pure CSS FAB Speed Dial (No JavaScript)
  9. 09Material Gradient Floating Action Button CSS
  10. 10Vertical and Horizontal Speed Dial FAB Menu CSS
  11. 11Morphing FAB Button That Expands to Pill CSS
  12. 12Scroll-to-Top Floating Button with SVG Progress Ring
  13. 13Floating Chat Widget FAB with Notification Panel
  14. 14Fan and Arc Radial Spread FAB Menu CSS
  15. 15Apple Squircle Superellipse Floating Button CSS
  16. 16Social Share Floating Button Cluster CSS
  17. 17Glassmorphism Floating Action Button with Backdrop Blur
  18. 18Cyberpunk Neon Glow Floating Button CSS
  19. 19Floating Compose Button with Tooltip on Hover CSS
  20. 20Draggable Floating Action Button with Corner Snap CSS
  21. 21Notification Bell Floating Button with Badge Counter
  22. 22Floating Cart Button with Live Badge Counter CSS
  23. 23Magnetic Cursor Follower Floating Button CSS
  24. 24Dark Mode Theme Toggle Floating Button CSS
  25. 25Classic Gradient Circle Plus FAB CSS
  26. 26Pulse Ring Attention-Getter Floating Button CSS
  27. 27Pill-Shaped FAB with Icon and Text Label CSS
  28. 28Neo-Brutalist Hard Shadow Floating Button CSS
  29. 29Floating Button with Loading Spinner State CSS
  30. 30Draggable Floating Button with 6-Dot Grip Handle CSS
  31. 31Voice Recording Microphone Floating Button CSS
  32. 32Aurora Gradient Drift Premium Floating Button CSS
  33. 33Quick Emoji Reply Reaction Floating Button CSS
  34. 34Modern Squircle Rounded Square FAB CSS
Pure CSSMIT licensed

Pure CSS FAB Speed Dial (No JavaScript)

A speed dial with a real open/close state, light dismiss, Escape handling and top-layer stacking — and not a line of script. The popover attribute gives you the behaviour for free, CSS anchor positioning places the menu against the button, and @starting-style animates it in from display:none. This is the pattern that finally makes the old checkbox hack obsolete.

Live Demo
Try it

Nucleo: 44,000+ SVG icons in five families · All Icons Bundle $149

Nucleo · Affiliate link

Get the Nucleo bundle

The code

<section class="fb-08" aria-label="Pure CSS speed dial demo">
  <div class="fb-08__page">
    <p class="fb-08__eyebrow">popover · anchor() · @starting-style</p>
    <h2 class="fb-08__title">Top layer, light dismiss, zero script</h2>
    <p class="fb-08__sub">Click the button, then click anywhere outside — the browser closes it, restores focus and keeps <code>aria-expanded</code> in sync on its own.</p>
    <p class="fb-08__chip">popovertarget · :popover-open · allow-discrete</p>
  </div>
  <div class="fb-08__dock">
    <ul class="fb-08__dial" id="fb-08-dial" popover>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></span>New task</button></li>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><rect x="4" y="5" width="16" height="15" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M8 3v4M16 3v4M4 10h16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span>Schedule</button></li>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M4 6h16M4 12h16M4 18h10" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></span>New list</button></li>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M12 3l2.4 6.2H21l-5.3 3.9 2 6.4-5.7-4-5.7 4 2-6.4L3 9.2h6.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg></span>Add favourite</button></li>
    </ul>
    <button class="fb-08__fab" id="fb-08-fab" type="button" popovertarget="fb-08-dial" aria-label="Quick create">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>
    </button>
  </div>
</section>
.fb-08 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-08-bg);
  --fb-08-bg: oklch(0.97 0.014 195);
  --fb-08-brand: oklch(0.58 0.13 195);
  --fb-08-brandDeep: oklch(0.44 0.12 200);
  --fb-08-ink: oklch(0.24 0.03 200);
  --fb-08-mut: oklch(0.53 0.02 200);
  --fb-08-card: oklch(1 0 0);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-08-ink);
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
}

.fb-08 *,
.fb-08 *::before,
.fb-08 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.fb-08__page {
  width: min(100%,56ch);
  display: grid;
  gap: 12px;
  justify-items: center;
  text-align: center;
}

.fb-08__eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--fb-08-brandDeep);
}

.fb-08__title {
  font-size: clamp(26px,4.6vw,44px);
  line-height: 1.05;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

.fb-08__sub {
  font-size: clamp(13.5px,1.7vw,16.5px);
  line-height: 1.62;
  color: var(--fb-08-mut);
  text-wrap: pretty;
}

.fb-08__sub code {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: .86em;
  background: oklch(0.58 0.13 195/.14);
  padding: 2px 6px;
  border-radius: 6px;
}

.fb-08__chip {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-08-mut);
  padding: 8px 14px;
  border: 1px solid oklch(0.24 0.03 200/.14);
  border-radius: 99px;
}

.fb-08__dock {
  position: absolute;
  right: clamp(18px,4vw,40px);
  bottom: calc(clamp(18px,4vw,40px) + env(safe-area-inset-bottom));
}

.fb-08__fab {
  anchor-name: --fb-08-anchor;
  display: grid;
  place-items: center;
  inline-size: 62px;
  block-size: 62px;
  border: 0;
  border-radius: 20px;
  cursor: pointer;
  color: oklch(0.99 0.01 195);
  background: linear-gradient(155deg,oklch(0.68 0.14 190),var(--fb-08-brandDeep));
  box-shadow: 0 14px 32px -12px oklch(0.44 0.12 200/.9),0 2px 6px oklch(0.3 0.06 200/.28);
  transition: transform .28s cubic-bezier(.2,1.3,.4,1),border-radius .28s ease;
}

.fb-08__fab svg {
  width: 27px;
  height: 27px;
  transition: rotate .32s cubic-bezier(.3,1.35,.4,1);
}

.fb-08__fab:hover {
  transform: translateY(-2px);
}

.fb-08__fab:active {
  transform: scale(.94);
}

.fb-08__fab:focus-visible {
  outline: 3px solid var(--fb-08-brandDeep);
  outline-offset: 4px;
}

.fb-08__fab[aria-expanded="true"] {
  border-radius: 50%;
}

.fb-08__fab[aria-expanded="true"] svg {
  rotate: 135deg;
}

.fb-08__dial {
  position: absolute;
  margin: 0;
  padding: 8px;
  border: 1px solid oklch(0.24 0.03 200/.1);
  border-radius: 20px;
  background: var(--fb-08-card);
  list-style: none;
  display: grid;
  gap: 4px;
  min-width: 210px;
  box-shadow: 0 26px 60px -24px oklch(0.24 0.03 200/.65),0 2px 6px oklch(0.24 0.03 200/.1);
  inset: auto;
  right: 0;
  bottom: 74px;
  opacity: 0;
  translate: 0 14px;
  scale: .96;
  transform-origin: 100% 100%;
  transition: opacity .26s ease,translate .3s cubic-bezier(.2,1.1,.3,1),scale .3s cubic-bezier(.2,1.1,.3,1),overlay .3s allow-discrete,display .3s allow-discrete;
}

.fb-08__dial:popover-open {
  opacity: 1;
  translate: 0 0;
  scale: 1;
}

@starting-style {
  .fb-08__dial:popover-open {
    opacity: 0;
    translate: 0 14px;
    scale: .96;
  }
}

@supports (anchor-name: --x) {
  .fb-08__dial {
    position-anchor: --fb-08-anchor;
    right: anchor(right);
    bottom: calc(anchor(top) + 12px);
    position-try-fallbacks: flip-block;
  }
}

.fb-08__act {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 46px;
  padding: 0 16px 0 8px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--fb-08-ink);
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background .16s ease,transform .16s ease;
}

.fb-08__act:hover {
  background: oklch(0.58 0.13 195/.1);
}

.fb-08__act:active {
  transform: scale(.98);
}

.fb-08__act:focus-visible {
  outline: 2.5px solid var(--fb-08-brandDeep);
  outline-offset: -2px;
}

.fb-08__ico {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  background: oklch(0.58 0.13 195/.12);
  color: var(--fb-08-brandDeep);
}

.fb-08__ico svg {
  width: 19px;
  height: 19px;
}

@media (prefers-reduced-motion: reduce) {
  .fb-08__dial,
    .fb-08__fab,
    .fb-08__fab svg {
    transition-duration: .01ms !important;
  }
}
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 Floating Button 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: Pure CSS FAB Speed Dial (No JavaScript)
Source: https://codefronts.com/components/css-floating-buttons/pure-css-fab-speed-dial-no-javascript/

A speed dial with a real open/close state, light dismiss, Escape handling and top-layer stacking — and not a line of script. The popover attribute gives you the behaviour for free, CSS anchor positioning places the menu against the button, and @starting-style animates it in from display:none. This is the pattern that finally makes the old checkbox hack obsolete.
## HTML
```html
<section class="fb-08" aria-label="Pure CSS speed dial demo">
  <div class="fb-08__page">
    <p class="fb-08__eyebrow">popover · anchor() · @starting-style</p>
    <h2 class="fb-08__title">Top layer, light dismiss, zero script</h2>
    <p class="fb-08__sub">Click the button, then click anywhere outside — the browser closes it, restores focus and keeps <code>aria-expanded</code> in sync on its own.</p>
    <p class="fb-08__chip">popovertarget · :popover-open · allow-discrete</p>
  </div>
  <div class="fb-08__dock">
    <ul class="fb-08__dial" id="fb-08-dial" popover>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></span>New task</button></li>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><rect x="4" y="5" width="16" height="15" rx="2.5" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M8 3v4M16 3v4M4 10h16" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></span>Schedule</button></li>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M4 6h16M4 12h16M4 18h10" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg></span>New list</button></li>
      <li><button class="fb-08__act" type="button"><span class="fb-08__ico" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><path d="M12 3l2.4 6.2H21l-5.3 3.9 2 6.4-5.7-4-5.7 4 2-6.4L3 9.2h6.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg></span>Add favourite</button></li>
    </ul>
    <button class="fb-08__fab" id="fb-08-fab" type="button" popovertarget="fb-08-dial" aria-label="Quick create">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2.4" stroke-linecap="round"/></svg>
    </button>
  </div>
</section>
```
## CSS
```css
.fb-08 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-08-bg);
  --fb-08-bg: oklch(0.97 0.014 195);
  --fb-08-brand: oklch(0.58 0.13 195);
  --fb-08-brandDeep: oklch(0.44 0.12 200);
  --fb-08-ink: oklch(0.24 0.03 200);
  --fb-08-mut: oklch(0.53 0.02 200);
  --fb-08-card: oklch(1 0 0);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-08-ink);
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
}

.fb-08 *,
.fb-08 *::before,
.fb-08 *::after {
  box-sizing: border-box;
  margin: 0;
  padding: 0;
}

.fb-08__page {
  width: min(100%,56ch);
  display: grid;
  gap: 12px;
  justify-items: center;
  text-align: center;
}

.fb-08__eyebrow {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11.5px;
  letter-spacing: .16em;
  text-transform: uppercase;
  color: var(--fb-08-brandDeep);
}

.fb-08__title {
  font-size: clamp(26px,4.6vw,44px);
  line-height: 1.05;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

.fb-08__sub {
  font-size: clamp(13.5px,1.7vw,16.5px);
  line-height: 1.62;
  color: var(--fb-08-mut);
  text-wrap: pretty;
}

.fb-08__sub code {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: .86em;
  background: oklch(0.58 0.13 195/.14);
  padding: 2px 6px;
  border-radius: 6px;
}

.fb-08__chip {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 11px;
  color: var(--fb-08-mut);
  padding: 8px 14px;
  border: 1px solid oklch(0.24 0.03 200/.14);
  border-radius: 99px;
}

.fb-08__dock {
  position: absolute;
  right: clamp(18px,4vw,40px);
  bottom: calc(clamp(18px,4vw,40px) + env(safe-area-inset-bottom));
}

.fb-08__fab {
  anchor-name: --fb-08-anchor;
  display: grid;
  place-items: center;
  inline-size: 62px;
  block-size: 62px;
  border: 0;
  border-radius: 20px;
  cursor: pointer;
  color: oklch(0.99 0.01 195);
  background: linear-gradient(155deg,oklch(0.68 0.14 190),var(--fb-08-brandDeep));
  box-shadow: 0 14px 32px -12px oklch(0.44 0.12 200/.9),0 2px 6px oklch(0.3 0.06 200/.28);
  transition: transform .28s cubic-bezier(.2,1.3,.4,1),border-radius .28s ease;
}

.fb-08__fab svg {
  width: 27px;
  height: 27px;
  transition: rotate .32s cubic-bezier(.3,1.35,.4,1);
}

.fb-08__fab:hover {
  transform: translateY(-2px);
}

.fb-08__fab:active {
  transform: scale(.94);
}

.fb-08__fab:focus-visible {
  outline: 3px solid var(--fb-08-brandDeep);
  outline-offset: 4px;
}

.fb-08__fab[aria-expanded="true"] {
  border-radius: 50%;
}

.fb-08__fab[aria-expanded="true"] svg {
  rotate: 135deg;
}

.fb-08__dial {
  position: absolute;
  margin: 0;
  padding: 8px;
  border: 1px solid oklch(0.24 0.03 200/.1);
  border-radius: 20px;
  background: var(--fb-08-card);
  list-style: none;
  display: grid;
  gap: 4px;
  min-width: 210px;
  box-shadow: 0 26px 60px -24px oklch(0.24 0.03 200/.65),0 2px 6px oklch(0.24 0.03 200/.1);
  inset: auto;
  right: 0;
  bottom: 74px;
  opacity: 0;
  translate: 0 14px;
  scale: .96;
  transform-origin: 100% 100%;
  transition: opacity .26s ease,translate .3s cubic-bezier(.2,1.1,.3,1),scale .3s cubic-bezier(.2,1.1,.3,1),overlay .3s allow-discrete,display .3s allow-discrete;
}

.fb-08__dial:popover-open {
  opacity: 1;
  translate: 0 0;
  scale: 1;
}

@starting-style {
  .fb-08__dial:popover-open {
    opacity: 0;
    translate: 0 14px;
    scale: .96;
  }
}

@supports (anchor-name: --x) {
  .fb-08__dial {
    position-anchor: --fb-08-anchor;
    right: anchor(right);
    bottom: calc(anchor(top) + 12px);
    position-try-fallbacks: flip-block;
  }
}

.fb-08__act {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 46px;
  padding: 0 16px 0 8px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--fb-08-ink);
  font-size: 14px;
  font-weight: 600;
  text-align: left;
  cursor: pointer;
  transition: background .16s ease,transform .16s ease;
}

.fb-08__act:hover {
  background: oklch(0.58 0.13 195/.1);
}

.fb-08__act:active {
  transform: scale(.98);
}

.fb-08__act:focus-visible {
  outline: 2.5px solid var(--fb-08-brandDeep);
  outline-offset: -2px;
}

.fb-08__ico {
  flex: none;
  width: 34px;
  height: 34px;
  border-radius: 11px;
  display: grid;
  place-items: center;
  background: oklch(0.58 0.13 195/.12);
  color: var(--fb-08-brandDeep);
}

.fb-08__ico svg {
  width: 19px;
  height: 19px;
}

@media (prefers-reduced-motion: reduce) {
  .fb-08__dial,
    .fb-08__fab,
    .fb-08__fab svg {
    transition-duration: .01ms !important;
  }
}
```

How this works

Three attributes do what used to take a small library:

<button popovertarget="dial" style="anchor-name:--fab">+</button>
<ul id="dial" popover style="position-anchor:--fab">…</ul>
[popover]{ position:absolute; inset:auto;
  bottom:anchor(--fab top); right:anchor(--fab right);
  opacity:0; translate:0 12px;
  transition:opacity .26s, translate .26s, overlay .26s allow-discrete, display .26s allow-discrete }
[popover]:popover-open{ opacity:1; translate:0 0 }
@starting-style{ [popover]:popover-open{ opacity:0; translate:0 12px } }

The browser handles the rest: the popover renders in the top layer (so it can never be clipped by an ancestor's overflow or lose a z-index war), clicking outside dismisses it, Escape closes it, and focus returns to the invoker. aria-expanded is even maintained by the browser on the invoking button.

@starting-style is what makes it animate: an element going from display:none normally has no "before" state to transition from. The rule supplies one. transition-behavior:allow-discrete on display and overlay keeps the element visible for the duration of the exit animation.

Anchor positioning removes the last reason to reach for JS: bottom:anchor(--fab top) means "my bottom edge sits on the button's top edge", recomputed automatically on scroll and resize.

Make it yours

  • Fallback placement: position-try-fallbacks: flip-block, flip-inline lets the dial flip above/below or left/right when it would overflow the viewport.
  • Direction: change the two anchor() edges — left:anchor(--fab right) gives a horizontal dial with the same markup.
  • No-anchor fallback: wrap the anchor rules in @supports (anchor-name:--x) and ship plain absolute positioning otherwise, as this demo does.
  • Hover-only hint: a popover="hint" popover shows on hover without stealing focus — useful for tooltips beside each action.

Gotchas — read before shipping

  • Without transition-behavior:allow-discrete (or the overlay transition) the exit animation is skipped entirely — the popover just disappears.
  • @starting-style rules must come after the base rule, and specificity still applies. A more specific later rule silently wins.
  • Anchor positioning needs the anchor to be in the same top-layer-friendly containing block; an overflow:hidden ancestor no longer clips a popover, but it can still clip a non-popover anchored element.
  • popover="auto" closes other open auto popovers. If you need two dials open at once, use popover="manual" and handle dismissal yourself.

Browser support

ChromeSafariFirefoxEdge
125+26+131+125+

Popover API: Chrome 114+, Safari 17+, Firefox 125+. @starting-style: Chrome 117+, Safari 17.5+, Firefox 129+. CSS anchor positioning: Chrome 125+, Safari 26+, Firefox 131+ — this demo wraps anchor rules in @supports and falls back to absolute positioning, so it works everywhere the popover API exists.

Techniques used in this demo

Search CodeFronts

Loading…