21 CSS Tooltips20 / 21

Pure CSSMIT licensed

Icon Toolbar

Unlabeled toolbar icons — bold, italic, link, image, code, list, settings — that reveal their action name on hover. The tooltip is a compact dark pill with a tail arrow, the workhorse pattern every desktop app ships and every web app should. For editors, design tools, dashboards.

Published

Live Demo
Try it

The code

<div class="tp-20">
  <div class="tp-20__wrap">
    <div class="tp-20__bar">
      <span class="tp-20__t">
        <button class="tp-20__btn tp-20__btn-active">B</button>
        <span class="tp-20__tip">Bold</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn" style="font-style:italic">I</button>
        <span class="tp-20__tip">Italic</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn" style="text-decoration:underline">U</button>
        <span class="tp-20__tip">Underline</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn" style="text-decoration:line-through;color:#666">S</button>
        <span class="tp-20__tip">Strikethrough</span>
      </span>
      <span class="tp-20__sep"></span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.354 5.5H4a3 3 0 0 0 0 6h3a3 3 0 0 0 2.83-4H9v.5A2 2 0 0 1 7 9.5H4a2 2 0 0 1 0-4h1.535c.218-.376.495-.717.82-1z"/><path d="M9 5.5a3 3 0 0 0-2.83 4h1.098A2 2 0 0 1 9 6.5h3a2 2 0 1 1 0 4h-1.535a4 4 0 0 1-.82 1H12a3 3 0 1 0 0-6H9z"/></svg></button>
        <span class="tp-20__tip">Insert link</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/><path d="M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z"/></svg></button>
        <span class="tp-20__tip">Insert image</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn tp-20__btn-code">&lt;/&gt;</button>
        <span class="tp-20__tip">Code block</span>
      </span>
      <span class="tp-20__sep"></span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg></button>
        <span class="tp-20__tip">Bullet list</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/><path d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.235.235 0 0 1 .02-.022z"/></svg></button>
        <span class="tp-20__tip">Checklist</span>
      </span>
      <span class="tp-20__sep"></span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/></svg></button>
        <span class="tp-20__tip">Preferences</span>
      </span>
    </div>
  </div>
</div>
.tp-20 {
  width: 100%;
  min-height: 100vh;
  background: #141414;
  /* Top room — labels pop UP from each button, ~30px tall. */
  padding: 80px 28px 60px;
  font-family: system-ui, -apple-system, sans-serif;
}

.tp-20__wrap {
  display: flex;
  justify-content: center;
}

.tp-20__bar {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  background: #1f1f1f;
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  padding: 5px;
}

.tp-20__sep {
  width: 1px;
  height: 20px;
  background: #2e2e2e;
  margin: 0 4px;
  flex-shrink: 0;
  display: inline-block;
}

.tp-20__t {
  position: relative;
  display: inline-block;
}

.tp-20__btn {
  width: 32px;
  height: 32px;
  border-radius: 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: help;
  color: #888;
  font-size: 13px;
  font-weight: 600;
  transition: background .12s, color .12s;
  border: none;
  background: transparent;
  letter-spacing: 0;
  font-family: inherit;
}

.tp-20__btn:hover {
  background: #2a2a2a;
  color: #e0e0e0;
}

.tp-20__btn-active {
  background: #2a2a2a;
  color: #fff;
}

.tp-20__btn-code {
  font-family: ui-monospace, monospace;
  font-size: 11px;
  font-weight: 700;
}

.tp-20__btn svg {
  width: 15px;
  height: 15px;
  fill: currentColor;
}

.tp-20__tip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: #18181b;
  color: #fff;
  font-size: 11.5px;
  padding: 5px 10px;
  border-radius: 5px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity .14s, transform .14s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear .14s;
  z-index: 100;
  line-height: 1;
  display: block;
}

.tp-20__t:hover .tp-20__tip {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  transition-delay: 0s;
}

.tp-20__tip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: #18181b;
}
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 Tooltip 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: Icon Toolbar
Source: https://codefronts.com/snippets/css-tooltips/icon-toolbar/

Unlabeled toolbar icons — bold, italic, link, image, code, list, settings — that reveal their action name on hover. The tooltip is a compact dark pill with a tail arrow, the workhorse pattern every desktop app ships and every web app should. For editors, design tools, dashboards.
## HTML
```html
<div class="tp-20">
  <div class="tp-20__wrap">
    <div class="tp-20__bar">
      <span class="tp-20__t">
        <button class="tp-20__btn tp-20__btn-active">B</button>
        <span class="tp-20__tip">Bold</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn" style="font-style:italic">I</button>
        <span class="tp-20__tip">Italic</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn" style="text-decoration:underline">U</button>
        <span class="tp-20__tip">Underline</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn" style="text-decoration:line-through;color:#666">S</button>
        <span class="tp-20__tip">Strikethrough</span>
      </span>
      <span class="tp-20__sep"></span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.354 5.5H4a3 3 0 0 0 0 6h3a3 3 0 0 0 2.83-4H9v.5A2 2 0 0 1 7 9.5H4a2 2 0 0 1 0-4h1.535c.218-.376.495-.717.82-1z"/><path d="M9 5.5a3 3 0 0 0-2.83 4h1.098A2 2 0 0 1 9 6.5h3a2 2 0 1 1 0 4h-1.535a4 4 0 0 1-.82 1H12a3 3 0 1 0 0-6H9z"/></svg></button>
        <span class="tp-20__tip">Insert link</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M6.002 5.5a1.5 1.5 0 1 1-3 0 1.5 1.5 0 0 1 3 0z"/><path d="M2.002 1a2 2 0 0 0-2 2v10a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V3a2 2 0 0 0-2-2h-12zm12 1a1 1 0 0 1 1 1v6.5l-3.777-1.947a.5.5 0 0 0-.577.093l-3.71 3.71-2.66-1.772a.5.5 0 0 0-.63.062L1.002 12V3a1 1 0 0 1 1-1h12z"/></svg></button>
        <span class="tp-20__tip">Insert image</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn tp-20__btn-code">&lt;/&gt;</button>
        <span class="tp-20__tip">Code block</span>
      </span>
      <span class="tp-20__sep"></span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path fill-rule="evenodd" d="M5 11.5a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm0-4a.5.5 0 0 1 .5-.5h9a.5.5 0 0 1 0 1h-9a.5.5 0 0 1-.5-.5zm-3 1a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2zm0 4a1 1 0 1 0 0-2 1 1 0 0 0 0 2z"/></svg></button>
        <span class="tp-20__tip">Bullet list</span>
      </span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M14 1a1 1 0 0 1 1 1v12a1 1 0 0 1-1 1H2a1 1 0 0 1-1-1V2a1 1 0 0 1 1-1h12zM2 0a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V2a2 2 0 0 0-2-2H2z"/><path d="M10.97 4.97a.75.75 0 0 1 1.071 1.05l-3.992 4.99a.75.75 0 0 1-1.08.02L4.324 8.384a.75.75 0 1 1 1.06-1.06l2.094 2.093 3.473-4.425a.235.235 0 0 1 .02-.022z"/></svg></button>
        <span class="tp-20__tip">Checklist</span>
      </span>
      <span class="tp-20__sep"></span>
      <span class="tp-20__t">
        <button class="tp-20__btn"><svg viewBox="0 0 16 16" aria-hidden="true"><path d="M9.405 1.05c-.413-1.4-2.397-1.4-2.81 0l-.1.34a1.464 1.464 0 0 1-2.105.872l-.31-.17c-1.283-.698-2.686.705-1.987 1.987l.169.311c.446.82.023 1.841-.872 2.105l-.34.1c-1.4.413-1.4 2.397 0 2.81l.34.1a1.464 1.464 0 0 1 .872 2.105l-.17.31c-.698 1.283.705 2.686 1.987 1.987l.311-.169a1.464 1.464 0 0 1 2.105.872l.1.34c.413 1.4 2.397 1.4 2.81 0l.1-.34a1.464 1.464 0 0 1 2.105-.872l.31.17c1.283.698 2.686-.705 1.987-1.987l-.169-.311a1.464 1.464 0 0 1 .872-2.105l.34-.1c1.4-.413 1.4-2.397 0-2.81l-.34-.1a1.464 1.464 0 0 1-.872-2.105l.17-.31c.698-1.283-.705-2.686-1.987-1.987l-.311.169a1.464 1.464 0 0 1-2.105-.872l-.1-.34zM8 10.93a2.929 2.929 0 1 1 0-5.86 2.929 2.929 0 0 1 0 5.858z"/></svg></button>
        <span class="tp-20__tip">Preferences</span>
      </span>
    </div>
  </div>
</div>
```
## CSS
```css
.tp-20 {
  width: 100%;
  min-height: 100vh;
  background: #141414;
  /* Top room — labels pop UP from each button, ~30px tall. */
  padding: 80px 28px 60px;
  font-family: system-ui, -apple-system, sans-serif;
}

.tp-20__wrap {
  display: flex;
  justify-content: center;
}

.tp-20__bar {
  display: inline-flex;
  align-items: center;
  gap: 2px;
  background: #1f1f1f;
  border: 1px solid #2a2a2a;
  border-radius: 8px;
  padding: 5px;
}

.tp-20__sep {
  width: 1px;
  height: 20px;
  background: #2e2e2e;
  margin: 0 4px;
  flex-shrink: 0;
  display: inline-block;
}

.tp-20__t {
  position: relative;
  display: inline-block;
}

.tp-20__btn {
  width: 32px;
  height: 32px;
  border-radius: 5px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: help;
  color: #888;
  font-size: 13px;
  font-weight: 600;
  transition: background .12s, color .12s;
  border: none;
  background: transparent;
  letter-spacing: 0;
  font-family: inherit;
}

.tp-20__btn:hover {
  background: #2a2a2a;
  color: #e0e0e0;
}

.tp-20__btn-active {
  background: #2a2a2a;
  color: #fff;
}

.tp-20__btn-code {
  font-family: ui-monospace, monospace;
  font-size: 11px;
  font-weight: 700;
}

.tp-20__btn svg {
  width: 15px;
  height: 15px;
  fill: currentColor;
}

.tp-20__tip {
  position: absolute;
  bottom: calc(100% + 8px);
  left: 50%;
  transform: translateX(-50%) translateY(4px);
  background: #18181b;
  color: #fff;
  font-size: 11.5px;
  padding: 5px 10px;
  border-radius: 5px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  visibility: hidden;
  transition: opacity .14s, transform .14s cubic-bezier(0.22, 1, 0.36, 1), visibility 0s linear .14s;
  z-index: 100;
  line-height: 1;
  display: block;
}

.tp-20__t:hover .tp-20__tip {
  opacity: 1;
  visibility: visible;
  transform: translateX(-50%) translateY(0);
  transition-delay: 0s;
}

.tp-20__tip::after {
  content: '';
  position: absolute;
  top: 100%;
  left: 50%;
  transform: translateX(-50%);
  border: 4px solid transparent;
  border-top-color: #18181b;
}
```

How this works

The tp-20 toolbar is the workhorse Figma/VS Code/Photoshop idiom: unlabeled icon buttons on a dark bar, each revealing a compact pill label above on hover. Structurally the bar is tp-20__bar, an inline flex row with 2px gaps, 5px padding, and a 1px #2a2a2a border on a #1f1f1f background. Each button lives inside tp-20__t, a relatively positioned inline-block that owns both the tp-20__btn trigger and the tp-20__tip tooltip. Section dividers use tp-20__sep, a 1px x 20px vertical rule with 4px horizontal margin.

Buttons are 32x32 with 5px rounded corners. The hover recipe is deliberately quiet: background lifts to #2a2a2a and color goes from #888 to #e0e0e0 over a 120ms transition. The tp-20__btn-active modifier freezes that hover state for the current selection (Bold, in the sample markup). SVGs sit inside each button and inherit fill: currentColor so the same colour transition drives icon and text-based buttons alike; the code button uses tp-20__btn-code to swap to a monospaced font for the code glyph.

The tip is tp-20__tip, an absolutely positioned pill anchored bottom: calc(100% + 8px) with left: 50% and translateX(-50%). Enter animation is faster than the card demos on this cluster — 140ms opacity plus a 4px translate-Y, chosen to match the desktop-app hover-intent expectation. The tail arrow is a pure CSS triangle rendered by tp-20__tip::after using a 4px transparent border with border-top-color: #18181b. That triangle points down from the tip toward the button, sealing the visual seam that expensive React tooltip libraries fake with SVG.

Make it yours

  • Replace the SVG icons by dropping in your own 16x16 viewBox glyphs. Because the SVGs inherit fill: currentColor from the button, the hover colour transition works on the icon body with no extra CSS.
  • Change section grouping by moving tp-20__sep to a new position in the bar. The dividers are inline elements, not flex tricks, so you can insert them anywhere in the markup without recalculating gap or padding.
  • Recolour for a light-theme toolbar by inverting the four dark tokens: #141414, #1f1f1f, #2a2a2a, and #18181b. Keep the same lightness deltas between each token so the hover and active states stay perceptibly distinct.
  • Anchor tips below the bar (for a top-docked toolbar) by swapping bottom: calc(100% + 8px) for top: calc(100% + 8px) on tp-20__tip, and flip the ::after arrow to border-bottom-color: #18181b with top: auto; bottom: 100%.
  • Add a keyboard-shortcut hint by splitting each tip into a label span and a shortcut span, using display: flex; gap: 8px; align-items: baseline. Give the shortcut span 0.7em size and 0.6 opacity to echo the VS Code sidebar treatment.

Gotchas — read before shipping

  • This is a hover-only pattern with no built-in hover-intent delay. Users who mouse across the toolbar to reach another region will flash six tooltips in a row. Add transition-delay: .35s on the tp-20__tip enter animation to gate reveal behind a real hover-intent window.
  • Buttons use cursor: help, but they are the primary action affordance — a Bold button should carry cursor: pointer and act on click. The help cursor is a legacy from tooltip conventions; keep the tooltip logic but restore cursor: pointer before shipping.
  • SVG icons ship without aria-label or accessible names. Screen readers will announce "button" without any hint of the action, failing SC 4.1.2 Name, Role, Value. Add aria-label or a visually-hidden span inside each tp-20__btn, and keep the visible tooltip for pointer users only.
  • The tooltip has pointer-events: none, which is correct for the hover-triggered pattern but blocks any hope of clickable content inside the pill. If you plan to add "Learn more" links inside a tip, this pattern is the wrong idiom — reach for a popover primitive instead.
  • white-space: nowrap on tp-20__tip means long localised labels can push past the container. German or Finnish translations of "Strikethrough" or "Insert image" can double the width and clip against the gallery frame. Switch to max-width: 220px plus wrapping when your locale bundle exceeds 12 chars.
  • The ::after arrow assumes the tip is centred over its button. If you translate the tip horizontally for edge buttons — the leftmost Bold or the rightmost Preferences — the arrow will misalign. Add per-edge modifiers (tp-20__tip--edge-left) that adjust ::after left independently of the pill.

Browser support

ChromeSafariFirefoxEdge
90+15+88+90+

Inline flex, currentColor SVG fill, and pure-CSS triangle arrows are all baseline and INP-safe under 200ms. The 140ms opacity/translate-Y transition renders identically on macOS and iOS. The ::after triangle sits crisply on Windows and Linux without subpixel wobble.

Techniques used in this demo

Search CodeFronts

Loading…