NavigationFree

Smart Tooltip System

Pure-CSS tooltips that automatically flip above or below, left- or right-aligned, whenever the trigger gets too close to a viewport edge.

CSSJSAnchor

Updated

Hover or click the scene to interact

This effect is free — the Navigation category contains 37 effects total, including 4 free. Effect.Labs has 840 vanilla effects. Explore the category →

Usage examples

Workspace
Smart Tooltip System — Position-aware
Tooltips appear below — no space above

① Toolbar — Tooltips flipped below header action buttons

WhenAction icons (save, share, preview…) are in an app bar at the top of the screen — the viewport leaves no room above them.
WhyWithout flipping, tooltips would escape the viewport and be invisible. The .flipped-bottom class, applied statically or detected by script, forces display below without repositioning the trigger.
Settings.flipped-bottom pre-applied on all toolbar buttons. Tooltip background #1e1b4b, offset 10px, transition 0.2s.
Users — recent activity
Name Status Last seen Actions
Sophie Martin Active Just now
Lucas Girard Pending 2 hours ago
Emma Leroux Inactive 3 days ago
Marc Dupont Active 5 min ago
Row 1 tooltips flip below (near top edge) — others stay above

② Data table — Automatic flip per row

WhenAction buttons (edit, delete) in a data table sit at variable heights — some close to the top edge of the scrolled container.
WhyThe detection script (getBoundingClientRect() on mouseenter) adds .flipped-bottom on top rows only and leaves default behavior on the rest — the tooltip picks its position on hover with no per-row manual configuration.
SettingsDetection threshold: trigger.top - container.top < 55px → add .flipped-bottom. No CSS or layout change on the table side.
Dashboard
2 841
Users
94 %
Uptime
18 ms
Response
Sidebar tooltips anchor right — never overflow left

③ Side navigation — Adaptive horizontal alignment

WhenA compact icon sidebar shows unlabeled icon buttons. Tooltips must appear to the right of the icons but align cleanly even on a panel pinned to the left edge of the screen.
WhyOn a left-edge trigger, the centered tooltip (left: 50%) would overflow left. The .flipped-right class anchors the tooltip to the trigger's right edge — it never escapes the viewport.
Settings.flipped-right pre-applied on left-panel icons. For a right-side panel, use .flipped-left in mirror.

How it works

Each trigger (.anchor-tooltip-trigger) is position: relative. The tooltip (.smart-tooltip) is position: absolute with bottom: calc(100% + 10px) — it appears above by default — and centered horizontally via left: 50%; transform: translateX(-50%). The arrow is a CSS triangle drawn by the ::after pseudo-element: four borders, three transparent, one opaque pointing toward the trigger.

Four override classes handle the positional variants. .flipped-bottom switches the anchor from bottom to top: calc(100% + 10px) (tooltip below) and reverses the arrow via border-top-color: transparent; border-bottom-color. .flipped-left switches from left: 50% to right: 0; left: auto; transform: none — arrow shifts to right: 14px. .flipped-right mirrors this behavior.

Visibility is driven by :hover and :focus via transition: opacity 0.2s, transform 0.2s — no JS needed for animation. For automatic overflow detection, a short script (~15 lines, zero dependencies) calls getBoundingClientRect() on the trigger and container on each mouseenter, then applies the appropriate .flipped-* class(es).

Browsers supporting CSS Anchor Positioning (@position-try + position-try-fallbacks, Chrome 125+) allow a fully CSS approach with no script: declare one @position-try block per variant and let the browser pick the first that doesn't overflow.

Accessibility

  • prefers-reduced-motion: the opacity 0.2s, transform 0.2s transition is not disabled in the base code. Add @media (prefers-reduced-motion: reduce) { .smart-tooltip { transition: none; } } for motion-sensitive users.
  • Keyboard triggering: the rule .anchor-tooltip-trigger:focus .smart-tooltip { opacity: 1 } is already in the CSS — the tooltip appears as soon as the button receives focus via Tab, no extra JS needed.
  • Contrast: background rgb(30, 27, 75) over text rgb(224, 231, 255) → ratio ≈ 11.5:1, well above the WCAG AA threshold (4.5:1).
  • Triggers are native <button> elements: focusable, announced by screen readers, and activatable with Space/Enter without extra ARIA.
  • No aria-describedby in the base code: tooltip text is not automatically read by screen readers. For tooltips carrying critical information, add aria-describedby="span-id" on the button and a matching id on the <span class="smart-tooltip">.

Browser compatibility

CSS positioning works in all modern browsers. getBoundingClientRect() overflow detection is universal. The all-CSS native mode (position-try-fallbacks) requires Chrome 125+.

Chrome 88+✓ Full
Firefox 90+✓ Full
Safari 15+✓ Full
Edge 88+✓ Full
Mobile iOS✓ Full
Android Chrome✓ Full

Without the detection script, tooltips always appear above — no JS errors, but no automatic flipping. The .flipped-* classes can be applied manually when position is known at design time.

The code

Copy the three blocks into your page. No dependencies.

HTML
<div class="anchor-demo">
    <div class="tooltip-scene" id="tooltipScene">
      <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
        Top
        <span class="smart-tooltip">Info above</span>
      </button>
      <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
        Edge
        <span class="smart-tooltip">Auto-flip if out of viewport</span>
      </button>
      <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
        Smart
        <span class="smart-tooltip">Adaptive position</span>
      </button>
    </div>
  </div>
CSS
.anchor-demo {
  position: relative;
  width: 100%;
  height: 220px;
  display: flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
}

.anchor-demo * {
  box-sizing: border-box;
}

.tooltip-scene {
  display: flex;
  gap: 32px;
  flex-wrap: wrap;
  justify-content: center;
  align-items: center;
  padding: 20px;
}

.anchor-tooltip-trigger {
  position: relative;
  padding: 10px 18px;
  background: linear-gradient(135deg, #6366f1, #8b5cf6);
  color: #fff;
  border: none;
  border-radius: 8px;
  font-size: 0.85rem;
  font-weight: 600;
  cursor: pointer;
  font-family: inherit;
}

.anchor-tooltip-trigger .smart-tooltip {
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  bottom: calc(100% + 10px);
  padding: 8px 14px;
  background: #1e1b4b;
  color: #e0e7ff;
  font-size: 0.78rem;
  font-weight: 500;
  border-radius: 8px;
  white-space: nowrap;
  pointer-events: none;
  opacity: 0;
  transition: opacity 0.2s ease, transform 0.2s ease;
  z-index: 100;
  box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35);
}

.anchor-tooltip-trigger .smart-tooltip::after {
  content: '';
  position: absolute;
  left: 50%;
  transform: translateX(-50%);
  top: 100%;
  border: 6px solid transparent;
  border-top-color: #1e1b4b;
}

.anchor-tooltip-trigger:hover .smart-tooltip,
.anchor-tooltip-trigger:focus .smart-tooltip {
  opacity: 1;
}
JavaScript (fx-0475)

Customize

Options passed to the API or data-* attributes:

Option / propertyDefaultEffect
background (CSS rule .smart-tooltip) rgb(30, 27, 75) Tooltip background — swap for your dark token or brand color.
color (CSS rule .smart-tooltip) rgb(224, 231, 255) Text color. Verify the contrast ratio with your chosen background (WCAG AA threshold: 4.5:1).
offset (calc(100% + Npx)) 10px Gap between tooltip and trigger. Increase for more breathing room, reduce for a denser style.
transition opacity 0.2s, transform 0.2s Duration and animated properties on show/hide. Reduce to 0.1s for snappier feedback, or set to none via prefers-reduced-motion media query.
font-size (.smart-tooltip) 0.78rem Text size. 0.78rem is legible without cluttering — don't go below 0.72rem.
.flipped-bottom absent Class to add to display the tooltip below the trigger. The arrow reverses automatically via ::after.
.flipped-left / .flipped-right absent Classes to anchor horizontal alignment when the tooltip would overflow a side edge. .flipped-left anchors to the trigger's right, .flipped-right anchors to the trigger's left.

FAQ

Is the tooltip truly CSS-only or does it need JavaScript?

Hover/focus animation and the four positional variants are 100% CSS. Automatic overflow detection — the part that picks the right variant on hover — needs a short script (~15 lines, zero dependencies). On Chrome 125+, position-try-fallbacks removes that need entirely with one @position-try block per variant.

How do I integrate this tooltip into a React or Vue component?

Wrap the trigger and tooltip span in a component. In the onMouseEnter handler, call getBoundingClientRect() on the tooltip element and update state to apply the .flipped-* classes. The effect CSS integrates without modification — no conflict with CSS-in-JS as long as class names remain stable.

Can the tooltip appear to the left or right of the trigger, not just above or below?

Not with the included classes — those adjust horizontal alignment but keep the tooltip above or below. For lateral positioning (left/right), add extra CSS rules: e.g. .flipped-side-right { left: calc(100% + 10px); bottom: auto; top: 50%; transform: translateY(-50%); } and a matching arrow.