Tooltip Pop

Tooltips pop up above icon buttons with a slight springy overshoot after a short pause, so they help without getting in the way.

  • CSS
  • Buttons
  • Hover
  • Added

AI prompt

Create icon-button tooltips that pop in with CSS. Each button is position: relative and holds a tooltip element with role="tooltip", referenced by aria-describedby on the button, positioned above it and centred with a small arrow from a rotated pseudo-element. At rest the tooltip has opacity 0, transform: translate(-50%, 4px) scale(0.85), transform-origin bottom centre, and pointer-events none. On :hover and :focus-visible, transition to opacity 1 and translate(-50%, 0) scale(1) over 220ms with cubic-bezier(0.34, 1.56, 0.64, 1) for a slight overshoot, after a 250ms delay so passing the pointer over the toolbar does not flash every tip. On the way out, use no delay and a 120ms fade. Under prefers-reduced-motion, fade without scaling. The tooltips read "Bold", "Italic", and "Add link".

Source

Demo.tsx
'use client'

import { useId } from 'react'
import s from './demo.module.css'

const ICONS = {
  bold: <path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zM7 12h7a3.5 3.5 0 0 1 0 7H7z" />,
  italic: <path d="M14 5h-4M14 19h-4M13.5 5l-3 14" />,
  link: <path d="M10 14a4 4 0 0 0 5.7 0l3-3a4 4 0 0 0-5.7-5.7l-1 1M14 10a4 4 0 0 0-5.7 0l-3 3a4 4 0 0 0 5.7 5.7l1-1" />,
}

export default function TooltipPop({ labels }: { labels: Record<string, string> }) {
  const id = useId()

  return (
    <div className={s.bar} role="toolbar" aria-label={labels.toolbar}>
      {(Object.keys(ICONS) as (keyof typeof ICONS)[]).map((k) => (
        <button key={k} type="button" className={s.btn} aria-label={labels[k]} aria-describedby={`${id}-${k}`}>
          <svg viewBox="0 0 24 24" aria-hidden="true">
            {ICONS[k]}
          </svg>
          <span id={`${id}-${k}`} role="tooltip" className={s.tip}>
            {labels[k]}
          </span>
        </button>
      ))}
    </div>
  )
}
demo.module.css
.bar {
  display: flex;
  gap: 4px;
  padding: 4px;
  border: 1px solid var(--stage-rule);
  border-radius: 12px;
  background: var(--stage-surface);
}

.btn {
  position: relative;
  display: grid;
  place-items: center;
  width: 44px;
  height: 44px;
  border: 0;
  border-radius: 8px;
  background: transparent;
  color: var(--stage-ink);
  cursor: pointer;
}

.btn:hover { background: var(--stage-bg); }
.btn:focus-visible { outline: 2px solid var(--color-focus); outline-offset: 1px; }

.btn svg {
  width: 18px;
  height: 18px;
  fill: none;
  stroke: currentColor;
  stroke-width: 2;
  stroke-linecap: round;
  stroke-linejoin: round;
}

.tip {
  position: absolute;
  left: 50%;
  bottom: calc(100% + 10px);
  padding: 6px 9px;
  border-radius: 6px;
  background: var(--stage-ink);
  color: var(--stage-bg);
  font: 500 var(--text-xs) / 1 var(--font-body);
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transform: translate(-50%, 4px) scale(0.85);
  transform-origin: 50% 100%;
  transition: opacity 120ms, transform 120ms;
}

.tip::after {
  content: '';
  position: absolute;
  left: 50%;
  top: 100%;
  width: 8px;
  height: 8px;
  margin: -4px 0 0 -4px;
  background: inherit;
  rotate: 45deg;
}

.btn:hover .tip,
.btn:focus-visible .tip {
  opacity: 1;
  transform: translate(-50%, 0) scale(1);
  transition: opacity 220ms 250ms, transform 220ms cubic-bezier(0.34, 1.56, 0.64, 1) 250ms;
}

@media (prefers-reduced-motion: reduce) {
  .tip, .btn:hover .tip, .btn:focus-visible .tip { transform: translate(-50%, 0); }
}

Suggest an idea

Seen a motion or a theme you would like to find here? Describe it, and it opens as a prefilled GitHub issue for you to post.

What are you suggesting?

Needs a GitHub account. Suggestions are public.