34 CSS Floating Buttons
11 / 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
Light JSMIT licensed

Morphing FAB Button That Expands to Pill CSS

A FAB that does not open a panel — it becomes one. Tap the circle and it stretches into a search pill with a live input and results; dismiss and it retracts into the same circle. Uses the View Transition API for the shared-element morph with a pure-CSS fallback, so the container feels continuous instead of one element vanishing while another appears.

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-11" aria-label="Morphing floating button demo">
  <div class="fb-11__page">
    <p class="fb-11__eyebrow">view-transition-name · shared element</p>
    <h2 class="fb-11__title">The button becomes the panel</h2>
    <p class="fb-11__sub">No fade-out, no pop-in. One container that stretches, keeps its identity, and hands focus to the field inside it.</p>
    <p class="fb-11__chip">startViewTransition() · Escape to retract</p>
  </div>
  <div class="fb-11__dock" id="fb-11-dock" data-open="false">
    <div class="fb-11__pill" id="fb-11-pill">
      <span class="fb-11__pIco" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><circle cx="11" cy="11" r="6.4" fill="none" stroke="currentColor" stroke-width="2"/><path d="M15.8 15.8L21 21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></span>
      <input class="fb-11__input" id="fb-11-input" type="text" placeholder="Search commands…" aria-label="Search commands" autocomplete="off">
      <button class="fb-11__close" id="fb-11-close" type="button" aria-label="Close search">×</button>
      <ul class="fb-11__results" id="fb-11-results">
        <li><button type="button"><kbd>⌘K</kbd>Open command palette</button></li>
        <li><button type="button"><kbd>N</kbd>New document</button></li>
        <li><button type="button"><kbd>T</kbd>Toggle theme</button></li>
      </ul>
    </div>
    <button class="fb-11__fab" id="fb-11-fab" type="button" aria-expanded="false" aria-controls="fb-11-pill" aria-label="Open search">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="6.4" fill="none" stroke="currentColor" stroke-width="2.2"/><path d="M15.8 15.8L21 21" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
    </button>
  </div>
</section>
.fb-11 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-11-bg);
  --fb-11-bg: linear-gradient(200deg,oklch(0.22 0.04 275),oklch(0.14 0.02 275) 65%);
  --fb-11-brand: oklch(0.74 0.16 165);
  --fb-11-ink: oklch(0.97 0.01 275);
  --fb-11-mut: oklch(0.75 0.02 275);
  --fb-11-card: oklch(0.24 0.03 275);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-11-ink);
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
  overflow: hidden;
}

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

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

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

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

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

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

.fb-11__dock {
  position: absolute;
  right: clamp(18px,4vw,44px);
  bottom: calc(clamp(18px,4vw,44px) + env(safe-area-inset-bottom));
  z-index: 5;
  display: grid;
  justify-items: end;
}

.fb-11__fab {
  view-transition-name: fb-11-morph;
  display: grid;
  place-items: center;
  inline-size: 62px;
  block-size: 62px;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: oklch(0.16 0.04 165);
  background: linear-gradient(150deg,oklch(0.86 0.14 155),var(--fb-11-brand));
  box-shadow: 0 14px 34px -12px oklch(0.74 0.16 165/.6),0 2px 6px oklch(0.1 0.02 275/.6);
  transition: transform .28s cubic-bezier(.2,1.3,.4,1),opacity .2s ease;
}

.fb-11__fab svg {
  width: 26px;
  height: 26px;
}

.fb-11__fab:hover {
  transform: translateY(-2px) scale(1.04);
}

.fb-11__fab:active {
  transform: scale(.93);
}

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

.fb-11__pill {
  display: none;
  view-transition-name: fb-11-morph;
  width: min(86vw,392px);
  padding: 10px 10px 10px 16px;
  border-radius: 26px;
  background: var(--fb-11-card);
  border: 1px solid oklch(1 0 0/.1);
  box-shadow: 0 30px 70px -26px oklch(0.05 0.02 275/.9),0 2px 6px oklch(0.05 0.02 275/.5);
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  transition: width .34s cubic-bezier(.2,.9,.3,1),border-radius .34s ease;
}

.fb-11__dock[data-open="true"] .fb-11__pill {
  display: grid;
}

.fb-11__dock[data-open="true"] .fb-11__fab {
  display: none;
}

.fb-11__pIco {
  display: grid;
  place-items: center;
  color: var(--fb-11-brand);
}

.fb-11__pIco svg {
  width: 20px;
  height: 20px;
}

.fb-11__input {
  grid-column: 2;
  min-height: 42px;
  border: 0;
  background: transparent;
  color: var(--fb-11-ink);
  font-size: 15.5px;
  font-family: inherit;
  outline: 0;
  min-width: 0;
}

.fb-11__input::placeholder {
  color: oklch(0.65 0.02 275);
}

.fb-11__input:focus-visible {
  outline: 2px solid var(--fb-11-brand);
  outline-offset: 4px;
  border-radius: 8px;
}

.fb-11__close {
  grid-column: 3;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: oklch(1 0 0/.07);
  color: var(--fb-11-mut);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.fb-11__close:hover {
  background: oklch(1 0 0/.14);
  color: var(--fb-11-ink);
}

.fb-11__close:focus-visible {
  outline: 2.5px solid var(--fb-11-brand);
  outline-offset: 2px;
}

.fb-11__results {
  grid-column: 1/-1;
  list-style: none;
  display: grid;
  gap: 2px;
  margin-top: 6px;
  border-top: 1px solid oklch(1 0 0/.08);
  padding-top: 8px;
}

.fb-11__results button {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--fb-11-ink);
  font-size: 14px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .16s ease;
}

.fb-11__results button:hover {
  background: oklch(1 0 0/.07);
}

.fb-11__results button:focus-visible {
  outline: 2.5px solid var(--fb-11-brand);
  outline-offset: -2px;
}

.fb-11__results kbd {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 10.5px;
  min-width: 32px;
  text-align: center;
  padding: 3px 6px;
  border-radius: 6px;
  background: oklch(1 0 0/.09);
  border: 1px solid oklch(1 0 0/.12);
  color: var(--fb-11-mut);
}

::view-transition-group(fb-11-morph) {
  animation-duration: .42s;
  animation-timing-function: cubic-bezier(.2,.9,.25,1);
}

@media (prefers-reduced-motion: reduce) {
  .fb-11 * {
    transition: none !important;
  }

  ::view-transition-group(fb-11-morph) {
    animation-duration: .01ms;
  }
}
(() => {
  const dock = document.getElementById('fb-11-dock');
  const fab = document.getElementById('fb-11-fab');
  const input = document.getElementById('fb-11-input');
  const close = document.getElementById('fb-11-close');
  if (!dock || !fab || fab.dataset.fbWired) return;
  fab.dataset.fbWired = '1';
  const apply = (open) => {
    dock.dataset.open = String(open);
    fab.setAttribute('aria-expanded', String(open));
  };
  const swap = (open) => {
    if (!document.startViewTransition) { apply(open); return Promise.resolve(); }
    return document.startViewTransition(() => apply(open)).finished;
  };
  fab.addEventListener('click', () => { swap(true).then(() => input.focus()); });
  close.addEventListener('click', () => { swap(false).then(() => fab.focus()); });
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') { swap(false).then(() => fab.focus()); }
  });
})();
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: Morphing FAB Button That Expands to Pill CSS
Source: https://codefronts.com/components/css-floating-buttons/morph-expand-fab/

A FAB that does not open a panel — it becomes one. Tap the circle and it stretches into a search pill with a live input and results; dismiss and it retracts into the same circle. Uses the View Transition API for the shared-element morph with a pure-CSS fallback, so the container feels continuous instead of one element vanishing while another appears.
## HTML
```html
<section class="fb-11" aria-label="Morphing floating button demo">
  <div class="fb-11__page">
    <p class="fb-11__eyebrow">view-transition-name · shared element</p>
    <h2 class="fb-11__title">The button becomes the panel</h2>
    <p class="fb-11__sub">No fade-out, no pop-in. One container that stretches, keeps its identity, and hands focus to the field inside it.</p>
    <p class="fb-11__chip">startViewTransition() · Escape to retract</p>
  </div>
  <div class="fb-11__dock" id="fb-11-dock" data-open="false">
    <div class="fb-11__pill" id="fb-11-pill">
      <span class="fb-11__pIco" aria-hidden="true"><svg viewBox="0 0 24 24" focusable="false"><circle cx="11" cy="11" r="6.4" fill="none" stroke="currentColor" stroke-width="2"/><path d="M15.8 15.8L21 21" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg></span>
      <input class="fb-11__input" id="fb-11-input" type="text" placeholder="Search commands…" aria-label="Search commands" autocomplete="off">
      <button class="fb-11__close" id="fb-11-close" type="button" aria-label="Close search">×</button>
      <ul class="fb-11__results" id="fb-11-results">
        <li><button type="button"><kbd>⌘K</kbd>Open command palette</button></li>
        <li><button type="button"><kbd>N</kbd>New document</button></li>
        <li><button type="button"><kbd>T</kbd>Toggle theme</button></li>
      </ul>
    </div>
    <button class="fb-11__fab" id="fb-11-fab" type="button" aria-expanded="false" aria-controls="fb-11-pill" aria-label="Open search">
      <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><circle cx="11" cy="11" r="6.4" fill="none" stroke="currentColor" stroke-width="2.2"/><path d="M15.8 15.8L21 21" stroke="currentColor" stroke-width="2.2" stroke-linecap="round"/></svg>
    </button>
  </div>
</section>
```
## CSS
```css
.fb-11 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-11-bg);
  --fb-11-bg: linear-gradient(200deg,oklch(0.22 0.04 275),oklch(0.14 0.02 275) 65%);
  --fb-11-brand: oklch(0.74 0.16 165);
  --fb-11-ink: oklch(0.97 0.01 275);
  --fb-11-mut: oklch(0.75 0.02 275);
  --fb-11-card: oklch(0.24 0.03 275);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-11-ink);
  position: relative;
  display: grid;
  place-items: center;
  padding: clamp(20px,5vw,56px);
  overflow: hidden;
}

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

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

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

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

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

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

.fb-11__dock {
  position: absolute;
  right: clamp(18px,4vw,44px);
  bottom: calc(clamp(18px,4vw,44px) + env(safe-area-inset-bottom));
  z-index: 5;
  display: grid;
  justify-items: end;
}

.fb-11__fab {
  view-transition-name: fb-11-morph;
  display: grid;
  place-items: center;
  inline-size: 62px;
  block-size: 62px;
  border: 0;
  border-radius: 50%;
  cursor: pointer;
  color: oklch(0.16 0.04 165);
  background: linear-gradient(150deg,oklch(0.86 0.14 155),var(--fb-11-brand));
  box-shadow: 0 14px 34px -12px oklch(0.74 0.16 165/.6),0 2px 6px oklch(0.1 0.02 275/.6);
  transition: transform .28s cubic-bezier(.2,1.3,.4,1),opacity .2s ease;
}

.fb-11__fab svg {
  width: 26px;
  height: 26px;
}

.fb-11__fab:hover {
  transform: translateY(-2px) scale(1.04);
}

.fb-11__fab:active {
  transform: scale(.93);
}

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

.fb-11__pill {
  display: none;
  view-transition-name: fb-11-morph;
  width: min(86vw,392px);
  padding: 10px 10px 10px 16px;
  border-radius: 26px;
  background: var(--fb-11-card);
  border: 1px solid oklch(1 0 0/.1);
  box-shadow: 0 30px 70px -26px oklch(0.05 0.02 275/.9),0 2px 6px oklch(0.05 0.02 275/.5);
  grid-template-columns: auto 1fr auto;
  align-items: center;
  gap: 10px;
  transition: width .34s cubic-bezier(.2,.9,.3,1),border-radius .34s ease;
}

.fb-11__dock[data-open="true"] .fb-11__pill {
  display: grid;
}

.fb-11__dock[data-open="true"] .fb-11__fab {
  display: none;
}

.fb-11__pIco {
  display: grid;
  place-items: center;
  color: var(--fb-11-brand);
}

.fb-11__pIco svg {
  width: 20px;
  height: 20px;
}

.fb-11__input {
  grid-column: 2;
  min-height: 42px;
  border: 0;
  background: transparent;
  color: var(--fb-11-ink);
  font-size: 15.5px;
  font-family: inherit;
  outline: 0;
  min-width: 0;
}

.fb-11__input::placeholder {
  color: oklch(0.65 0.02 275);
}

.fb-11__input:focus-visible {
  outline: 2px solid var(--fb-11-brand);
  outline-offset: 4px;
  border-radius: 8px;
}

.fb-11__close {
  grid-column: 3;
  width: 38px;
  height: 38px;
  border: 0;
  border-radius: 50%;
  background: oklch(1 0 0/.07);
  color: var(--fb-11-mut);
  font-size: 20px;
  line-height: 1;
  cursor: pointer;
  transition: background .18s ease,color .18s ease;
}

.fb-11__close:hover {
  background: oklch(1 0 0/.14);
  color: var(--fb-11-ink);
}

.fb-11__close:focus-visible {
  outline: 2.5px solid var(--fb-11-brand);
  outline-offset: 2px;
}

.fb-11__results {
  grid-column: 1/-1;
  list-style: none;
  display: grid;
  gap: 2px;
  margin-top: 6px;
  border-top: 1px solid oklch(1 0 0/.08);
  padding-top: 8px;
}

.fb-11__results button {
  display: flex;
  align-items: center;
  gap: 11px;
  width: 100%;
  min-height: 44px;
  padding: 0 12px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--fb-11-ink);
  font-size: 14px;
  font-family: inherit;
  text-align: left;
  cursor: pointer;
  transition: background .16s ease;
}

.fb-11__results button:hover {
  background: oklch(1 0 0/.07);
}

.fb-11__results button:focus-visible {
  outline: 2.5px solid var(--fb-11-brand);
  outline-offset: -2px;
}

.fb-11__results kbd {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 10.5px;
  min-width: 32px;
  text-align: center;
  padding: 3px 6px;
  border-radius: 6px;
  background: oklch(1 0 0/.09);
  border: 1px solid oklch(1 0 0/.12);
  color: var(--fb-11-mut);
}

::view-transition-group(fb-11-morph) {
  animation-duration: .42s;
  animation-timing-function: cubic-bezier(.2,.9,.25,1);
}

@media (prefers-reduced-motion: reduce) {
  .fb-11 * {
    transition: none !important;
  }

  ::view-transition-group(fb-11-morph) {
    animation-duration: .01ms;
  }
}
```

## JavaScript
```js
(() => {
  const dock = document.getElementById('fb-11-dock');
  const fab = document.getElementById('fb-11-fab');
  const input = document.getElementById('fb-11-input');
  const close = document.getElementById('fb-11-close');
  if (!dock || !fab || fab.dataset.fbWired) return;
  fab.dataset.fbWired = '1';
  const apply = (open) => {
    dock.dataset.open = String(open);
    fab.setAttribute('aria-expanded', String(open));
  };
  const swap = (open) => {
    if (!document.startViewTransition) { apply(open); return Promise.resolve(); }
    return document.startViewTransition(() => apply(open)).finished;
  };
  fab.addEventListener('click', () => { swap(true).then(() => input.focus()); });
  close.addEventListener('click', () => { swap(false).then(() => fab.focus()); });
  input.addEventListener('keydown', (e) => {
    if (e.key === 'Escape') { swap(false).then(() => fab.focus()); }
  });
})();
```

How this works

Both states share a view-transition-name, which is all the browser needs to tween between them:

.fab, .pill{ view-transition-name:fb-morph }
btn.onclick = () => {
  if (!document.startViewTransition) return swap();
  document.startViewTransition(() => swap());
};

Inside the callback you simply swap which element is in the DOM. The browser snapshots the old state, applies the new one, and animates position, size and border-radius between the two snapshots — a container morph with no FLIP maths and no animation library.

The fallback matters, because the morph should not be load-bearing. Without startViewTransition the same swap runs inside a CSS transition on inline-size and border-radius, which gets you 80% of the effect. Feature-detect, never assume.

Focus management is the part that makes it usable: on expand, focus moves into the input; on collapse, it returns to the FAB. Escape collapses. The input is a bare input with a sibling button — no form element, so a stray Enter key can never navigate the page away.

Make it yours

  • Morph target: the same technique turns a FAB into a full compose sheet, a colour picker, or a mini player. Only the expanded markup changes.
  • Duration: ::view-transition-group(fb-morph){ animation-duration:.42s } tunes the morph independently of everything else on the page.
  • Direction: anchor transform-origin and the pill's inset-inline-end to the FAB's corner so it always grows away from the screen edge.
  • Result list: the panel under the pill can animate its own rows with a staggered @starting-style for a layered reveal.

Gotchas — read before shipping

  • view-transition-name must be unique per page at any given moment. Two visible elements sharing one name aborts the transition with a console error.
  • startViewTransition takes a callback that performs the DOM change synchronously — awaiting a fetch inside it holds the page frozen on the old snapshot.
  • Autofocusing an input on expand opens the mobile keyboard, which can push a bottom-anchored pill off-screen. Focus after the transition finishes, and keep the pill above the keyboard inset.
  • Never place the input inside a form in an embedded playground — a submit reloads the frame. A button plus keydown handler is enough.

Browser support

ChromeSafariFirefoxEdge
114+17.5+121+114+

Floor set by oklch(), text-wrap as this demo is written. The core technique itself goes back further — Chrome 111+.

Same-document view transitions: Chrome 111+, Safari 18+, Firefox 144+. Everywhere else the CSS transition fallback runs — the feature detection is two lines and this demo ships it.

Search CodeFronts

Loading…