34 CSS Floating Buttons
19 / 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

Floating Compose Button with Tooltip on Hover CSS

An icon-only FAB is only usable if it can say what it does. This is the tooltip done to spec: it appears on hover and keyboard focus, waits 400ms before showing but disappears instantly, never covers the button, flips when it would leave the viewport, and — critically — the tooltip text is not the accessible name, it is a description of it.

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-19" aria-label="Floating compose button with tooltip demo">
  <div class="fb-19__app">
    <aside class="fb-19__rail">
      <button class="fb-19__railBtn" type="button" aria-label="Inbox" aria-describedby="fb-19-t1"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 12l3-7h12l3 7v6H3z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M3 12h5l1.4 2.4h5.2L16 12h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg></button>
      <span class="fb-19__tip fb-19__tip--rail" id="fb-19-t1" role="tooltip">Inbox <kbd>G</kbd> <kbd>I</kbd></span>
      <button class="fb-19__railBtn" type="button" aria-label="Starred" aria-describedby="fb-19-t2"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3.6l2.5 5.4 5.9.6-4.4 4 1.2 5.8L12 16.4 6.8 19.4 8 13.6l-4.4-4 5.9-.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg></button>
      <span class="fb-19__tip fb-19__tip--rail" id="fb-19-t2" role="tooltip">Starred <kbd>G</kbd> <kbd>S</kbd></span>
      <button class="fb-19__railBtn" type="button" aria-label="Archive" aria-describedby="fb-19-t3"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="3" y="4" width="18" height="4.5" rx="1.4" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M5 8.5V19h14V8.5M10 12h4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></button>
      <span class="fb-19__tip fb-19__tip--rail" id="fb-19-t3" role="tooltip">Archive <kbd>E</kbd></span>
    </aside>
    <div class="fb-19__main">
      <p class="fb-19__eyebrow">400ms in · 0ms out · flip-inline</p>
      <h2 class="fb-19__title">A tooltip is a description, not a name</h2>
      <p class="fb-19__sub">Hover the rail, then tab through it. Same tooltip, same timing, and the button still announces its own label first.</p>
      <p class="fb-19__chip">aria-describedby + aria-label · anchor()</p>
    </div>
    <div class="fb-19__dock">
      <button class="fb-19__fab" id="fb-19-fab" type="button" aria-label="Compose" aria-describedby="fb-19-tip">
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 20h4L18.5 9.5l-4-4L4 16z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"/><path d="M14.5 5.5l4 4" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>
      </button>
      <span class="fb-19__tip" id="fb-19-tip" role="tooltip"><b>Compose</b><kbd>C</kbd></span>
    </div>
  </div>
</section>
.fb-19 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-19-bg);
  --fb-19-bg: oklch(0.96 0.008 265);
  --fb-19-brand: oklch(0.52 0.16 258);
  --fb-19-ink: oklch(0.22 0.02 262);
  --fb-19-mut: oklch(0.53 0.015 262);
  --fb-19-card: oklch(1 0 0);
  --fb-19-tip: oklch(0.24 0.03 262);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-19-ink);
  display: grid;
  place-items: center;
  padding: clamp(18px,4vw,48px);
}

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

.fb-19__app {
  position: relative;
  width: min(100%,760px);
  min-height: min(62svh,520px);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: clamp(16px,3vw,32px);
  padding: clamp(18px,3vw,26px);
  border-radius: 26px;
  background: var(--fb-19-card);
  border: 1px solid oklch(0.22 0.02 262/.09);
  box-shadow: 0 26px 60px -34px oklch(0.22 0.02 262/.85);
}

.fb-19__rail {
  position: relative;
  display: grid;
  align-content: start;
  gap: 10px;
  padding: 10px;
  border-radius: 20px;
  background: oklch(0.97 0.008 262);
  border: 1px solid oklch(0.22 0.02 262/.07);
}

.fb-19__railBtn {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--fb-19-mut);
  cursor: pointer;
  transition: background .16s ease,color .16s ease,transform .16s ease;
}

.fb-19__railBtn svg {
  width: 22px;
  height: 22px;
}

.fb-19__railBtn:hover {
  background: oklch(0.52 0.16 258/.1);
  color: var(--fb-19-brand);
}

.fb-19__railBtn:active {
  transform: scale(.94);
}

.fb-19__railBtn:focus-visible {
  outline: 3px solid var(--fb-19-brand);
  outline-offset: 2px;
}

.fb-19__main {
  display: grid;
  align-content: center;
  gap: 12px;
  justify-items: start;
  padding-right: clamp(0px,8vw,90px);
}

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

.fb-19__title {
  font-size: clamp(23px,3.8vw,38px);
  line-height: 1.06;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

.fb-19__sub {
  max-width: 44ch;
  font-size: clamp(13.5px,1.6vw,16px);
  line-height: 1.62;
  color: var(--fb-19-mut);
  text-wrap: pretty;
}

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

.fb-19__dock {
  position: absolute;
  right: clamp(16px,3vw,28px);
  bottom: clamp(16px,3vw,28px);
}

.fb-19__fab {
  anchor-name: --fb-19-anchor;
  display: grid;
  place-items: center;
  inline-size: 60px;
  block-size: 60px;
  border: 0;
  border-radius: 20px;
  cursor: pointer;
  color: oklch(0.99 0.01 258);
  background: linear-gradient(150deg,oklch(0.62 0.17 254),oklch(0.42 0.16 262));
  box-shadow: 0 14px 32px -12px oklch(0.42 0.16 262/.85),0 2px 5px oklch(0.3 0.06 262/.25);
  transition: transform .28s cubic-bezier(.2,1.3,.4,1);
}

.fb-19__fab svg {
  width: 25px;
  height: 25px;
}

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

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

.fb-19__fab:focus-visible {
  outline: 3px solid oklch(0.42 0.16 262);
  outline-offset: 4px;
}

.fb-19__tip {
  position: absolute;
  z-index: 8;
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  padding: 8px 12px;
  border-radius: 10px;
  background: var(--fb-19-tip);
  color: oklch(0.98 0.01 262);
  font-size: 12.5px;
  font-weight: 600;
  pointer-events: none;
  opacity: 0;
  box-shadow: 0 8px 22px -10px oklch(0.22 0.02 262/.9);
  right: calc(100% + 12px);
  top: 50%;
  translate: -8px -50%;
  transition: opacity .16s ease .4s,translate .2s cubic-bezier(.2,.9,.3,1) .4s;
}

.fb-19__tip::after {
  content: "";
  position: absolute;
  left: 100%;
  top: 50%;
  translate: -3px -50%;
  width: 8px;
  height: 8px;
  background: var(--fb-19-tip);
  rotate: 45deg;
  border-radius: 1px;
}

.fb-19__tip kbd {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 10.5px;
  padding: 2px 6px;
  border-radius: 5px;
  background: oklch(1 0 0/.14);
  border: 1px solid oklch(1 0 0/.16);
}

.fb-19__fab:is(:hover,:focus-visible) + .fb-19__tip,
.fb-19__railBtn:is(:hover,:focus-visible) + .fb-19__tip {
  opacity: 1;
  translate: 0 -50%;
  transition-delay: 0s,0s;
}

.fb-19__tip--rail {
  right: auto;
  left: calc(100% + 12px);
  translate: 8px -50%;
  margin-top: -34px;
}

.fb-19__tip--rail::after {
  left: auto;
  right: 100%;
  translate: 3px -50%;
}

.fb-19__railBtn:is(:hover,:focus-visible) + .fb-19__tip--rail {
  translate: 0 -50%;
}

@supports (anchor-name: --x) {
  .fb-19__tip {
    position-anchor: --fb-19-anchor;
    position-try-fallbacks: flip-inline;
    right: anchor(left);
    top: anchor(center);
    inset-inline-start: auto;
  }
}

@media (max-width: 640px) {
  .fb-19__app {
    grid-template-columns: 1fr;
  }

  .fb-19__rail {
    grid-auto-flow: column;
    justify-content: start;
  }

  .fb-19__tip--rail {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .fb-19 * {
    transition: none !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: Floating Compose Button with Tooltip on Hover CSS
Source: https://codefronts.com/components/css-floating-buttons/compose-tooltip-fab/

An icon-only FAB is only usable if it can say what it does. This is the tooltip done to spec: it appears on hover and keyboard focus, waits 400ms before showing but disappears instantly, never covers the button, flips when it would leave the viewport, and — critically — the tooltip text is not the accessible name, it is a description of it.
## HTML
```html
<section class="fb-19" aria-label="Floating compose button with tooltip demo">
  <div class="fb-19__app">
    <aside class="fb-19__rail">
      <button class="fb-19__railBtn" type="button" aria-label="Inbox" aria-describedby="fb-19-t1"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M3 12l3-7h12l3 7v6H3z" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/><path d="M3 12h5l1.4 2.4h5.2L16 12h5" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linejoin="round"/></svg></button>
      <span class="fb-19__tip fb-19__tip--rail" id="fb-19-t1" role="tooltip">Inbox <kbd>G</kbd> <kbd>I</kbd></span>
      <button class="fb-19__railBtn" type="button" aria-label="Starred" aria-describedby="fb-19-t2"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M12 3.6l2.5 5.4 5.9.6-4.4 4 1.2 5.8L12 16.4 6.8 19.4 8 13.6l-4.4-4 5.9-.6z" fill="none" stroke="currentColor" stroke-width="1.7" stroke-linejoin="round"/></svg></button>
      <span class="fb-19__tip fb-19__tip--rail" id="fb-19-t2" role="tooltip">Starred <kbd>G</kbd> <kbd>S</kbd></span>
      <button class="fb-19__railBtn" type="button" aria-label="Archive" aria-describedby="fb-19-t3"><svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><rect x="3" y="4" width="18" height="4.5" rx="1.4" fill="none" stroke="currentColor" stroke-width="1.8"/><path d="M5 8.5V19h14V8.5M10 12h4" fill="none" stroke="currentColor" stroke-width="1.8" stroke-linecap="round"/></svg></button>
      <span class="fb-19__tip fb-19__tip--rail" id="fb-19-t3" role="tooltip">Archive <kbd>E</kbd></span>
    </aside>
    <div class="fb-19__main">
      <p class="fb-19__eyebrow">400ms in · 0ms out · flip-inline</p>
      <h2 class="fb-19__title">A tooltip is a description, not a name</h2>
      <p class="fb-19__sub">Hover the rail, then tab through it. Same tooltip, same timing, and the button still announces its own label first.</p>
      <p class="fb-19__chip">aria-describedby + aria-label · anchor()</p>
    </div>
    <div class="fb-19__dock">
      <button class="fb-19__fab" id="fb-19-fab" type="button" aria-label="Compose" aria-describedby="fb-19-tip">
        <svg viewBox="0 0 24 24" aria-hidden="true" focusable="false"><path d="M4 20h4L18.5 9.5l-4-4L4 16z" fill="none" stroke="currentColor" stroke-width="1.9" stroke-linejoin="round"/><path d="M14.5 5.5l4 4" stroke="currentColor" stroke-width="1.9" stroke-linecap="round"/></svg>
      </button>
      <span class="fb-19__tip" id="fb-19-tip" role="tooltip"><b>Compose</b><kbd>C</kbd></span>
    </div>
  </div>
</section>
```
## CSS
```css
.fb-19 {
  width: 100%;
  min-height: 100vh;
  min-height: 100svh;
  display: block;
  background: var(--fb-19-bg);
  --fb-19-bg: oklch(0.96 0.008 265);
  --fb-19-brand: oklch(0.52 0.16 258);
  --fb-19-ink: oklch(0.22 0.02 262);
  --fb-19-mut: oklch(0.53 0.015 262);
  --fb-19-card: oklch(1 0 0);
  --fb-19-tip: oklch(0.24 0.03 262);
  font-family: 'Segoe UI',system-ui,-apple-system,sans-serif;
  color: var(--fb-19-ink);
  display: grid;
  place-items: center;
  padding: clamp(18px,4vw,48px);
}

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

.fb-19__app {
  position: relative;
  width: min(100%,760px);
  min-height: min(62svh,520px);
  display: grid;
  grid-template-columns: auto 1fr;
  gap: clamp(16px,3vw,32px);
  padding: clamp(18px,3vw,26px);
  border-radius: 26px;
  background: var(--fb-19-card);
  border: 1px solid oklch(0.22 0.02 262/.09);
  box-shadow: 0 26px 60px -34px oklch(0.22 0.02 262/.85);
}

.fb-19__rail {
  position: relative;
  display: grid;
  align-content: start;
  gap: 10px;
  padding: 10px;
  border-radius: 20px;
  background: oklch(0.97 0.008 262);
  border: 1px solid oklch(0.22 0.02 262/.07);
}

.fb-19__railBtn {
  display: grid;
  place-items: center;
  width: 48px;
  height: 48px;
  border: 0;
  border-radius: 14px;
  background: transparent;
  color: var(--fb-19-mut);
  cursor: pointer;
  transition: background .16s ease,color .16s ease,transform .16s ease;
}

.fb-19__railBtn svg {
  width: 22px;
  height: 22px;
}

.fb-19__railBtn:hover {
  background: oklch(0.52 0.16 258/.1);
  color: var(--fb-19-brand);
}

.fb-19__railBtn:active {
  transform: scale(.94);
}

.fb-19__railBtn:focus-visible {
  outline: 3px solid var(--fb-19-brand);
  outline-offset: 2px;
}

.fb-19__main {
  display: grid;
  align-content: center;
  gap: 12px;
  justify-items: start;
  padding-right: clamp(0px,8vw,90px);
}

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

.fb-19__title {
  font-size: clamp(23px,3.8vw,38px);
  line-height: 1.06;
  letter-spacing: -.03em;
  font-weight: 700;
  text-wrap: balance;
}

.fb-19__sub {
  max-width: 44ch;
  font-size: clamp(13.5px,1.6vw,16px);
  line-height: 1.62;
  color: var(--fb-19-mut);
  text-wrap: pretty;
}

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

.fb-19__dock {
  position: absolute;
  right: clamp(16px,3vw,28px);
  bottom: clamp(16px,3vw,28px);
}

.fb-19__fab {
  anchor-name: --fb-19-anchor;
  display: grid;
  place-items: center;
  inline-size: 60px;
  block-size: 60px;
  border: 0;
  border-radius: 20px;
  cursor: pointer;
  color: oklch(0.99 0.01 258);
  background: linear-gradient(150deg,oklch(0.62 0.17 254),oklch(0.42 0.16 262));
  box-shadow: 0 14px 32px -12px oklch(0.42 0.16 262/.85),0 2px 5px oklch(0.3 0.06 262/.25);
  transition: transform .28s cubic-bezier(.2,1.3,.4,1);
}

.fb-19__fab svg {
  width: 25px;
  height: 25px;
}

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

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

.fb-19__fab:focus-visible {
  outline: 3px solid oklch(0.42 0.16 262);
  outline-offset: 4px;
}

.fb-19__tip {
  position: absolute;
  z-index: 8;
  display: flex;
  align-items: center;
  gap: 8px;
  white-space: nowrap;
  padding: 8px 12px;
  border-radius: 10px;
  background: var(--fb-19-tip);
  color: oklch(0.98 0.01 262);
  font-size: 12.5px;
  font-weight: 600;
  pointer-events: none;
  opacity: 0;
  box-shadow: 0 8px 22px -10px oklch(0.22 0.02 262/.9);
  right: calc(100% + 12px);
  top: 50%;
  translate: -8px -50%;
  transition: opacity .16s ease .4s,translate .2s cubic-bezier(.2,.9,.3,1) .4s;
}

.fb-19__tip::after {
  content: "";
  position: absolute;
  left: 100%;
  top: 50%;
  translate: -3px -50%;
  width: 8px;
  height: 8px;
  background: var(--fb-19-tip);
  rotate: 45deg;
  border-radius: 1px;
}

.fb-19__tip kbd {
  font-family: ui-monospace,Menlo,Consolas,monospace;
  font-size: 10.5px;
  padding: 2px 6px;
  border-radius: 5px;
  background: oklch(1 0 0/.14);
  border: 1px solid oklch(1 0 0/.16);
}

.fb-19__fab:is(:hover,:focus-visible) + .fb-19__tip,
.fb-19__railBtn:is(:hover,:focus-visible) + .fb-19__tip {
  opacity: 1;
  translate: 0 -50%;
  transition-delay: 0s,0s;
}

.fb-19__tip--rail {
  right: auto;
  left: calc(100% + 12px);
  translate: 8px -50%;
  margin-top: -34px;
}

.fb-19__tip--rail::after {
  left: auto;
  right: 100%;
  translate: 3px -50%;
}

.fb-19__railBtn:is(:hover,:focus-visible) + .fb-19__tip--rail {
  translate: 0 -50%;
}

@supports (anchor-name: --x) {
  .fb-19__tip {
    position-anchor: --fb-19-anchor;
    position-try-fallbacks: flip-inline;
    right: anchor(left);
    top: anchor(center);
    inset-inline-start: auto;
  }
}

@media (max-width: 640px) {
  .fb-19__app {
    grid-template-columns: 1fr;
  }

  .fb-19__rail {
    grid-auto-flow: column;
    justify-content: start;
  }

  .fb-19__tip--rail {
    display: none;
  }
}

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

How this works

Anchor positioning removes all the offset maths, and the delay is asymmetric:

.fab{ anchor-name:--compose }
.tip{ position:absolute; position-anchor:--compose;
      right:anchor(left); bottom:anchor(center);
      translate:-10px 50%; opacity:0;
      transition:opacity .16s .4s, translate .2s .4s }   /* delay in */
.fab:is(:hover,:focus-visible) + .tip{ opacity:1; translate:0 50%;
      transition-delay:0s }                              /* no delay out */

The 400ms in-delay stops tooltips from strobing as the pointer crosses a toolbar; zero out-delay means they never linger over content the user is trying to reach. Getting this asymmetry right is most of what separates a good tooltip from an annoying one.

On the accessibility side: the button already has aria-label="Compose". The tooltip carries the same word plus the shortcut, and is linked with aria-describedby — so a screen reader announces "Compose, button, Compose. Keyboard shortcut C" rather than nothing, and voice-control users can still say "click Compose".

position-try-fallbacks: flip-inline handles the edge case: near the right edge the tooltip flips to the other side automatically, with no measurement code.

Make it yours

  • Placement: change the two anchor() edges. bottom:anchor(top) puts it above; left:anchor(right) to the right.
  • Rich tooltips: add a second line with the shortcut in a . Keep total width under 32ch or it becomes a popover, not a tip.
  • Delay: 400ms in is the desktop convention. For a single isolated FAB, 150ms feels more responsive without strobing.
  • Touch: tooltips do not exist on touch. Pair the FAB with a visible label below 640px rather than relying on hover.

Gotchas — read before shipping

  • Never put the only label in the tooltip. Icon-only buttons need aria-label; the tooltip is aria-describedby on top of it.
  • title attributes are not tooltips — they are inconsistent, unstyleable, delayed by ~1s and skipped by many screen readers.
  • A tooltip that appears under the pointer can flicker as the pointer enters it. Offset by at least 8px and set pointer-events:none.
  • Tooltips must be dismissible with Escape when triggered by keyboard focus, or they can permanently obscure content.

Browser support

ChromeSafariFirefoxEdge
125+26+131+125+

CSS anchor positioning: Chrome 125+, Safari 26+, Firefox 131+. This demo wraps anchor rules in @supports and falls back to plain absolute positioning, so the tooltip works in every browser.

Search CodeFronts

Loading…