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
Hover or click the scene to interact

This effect is free — the Navigation category contains 36 effects total, including 4 free. Effect.Labs has 811 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 <code>.flipped-bottom</code> 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 (<code>getBoundingClientRect()</code> on <code>mouseenter</code>) adds <code>.flipped-bottom</code> 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 (<code>left: 50%</code>) would overflow left. The <code>.flipped-right</code> 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 <code>.flipped-*</code> 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 au-dessus</span>
    </button>
    <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
      Edge
      <span class="smart-tooltip">Auto-flip si hors viewport</span>
    </button>
    <button class="anchor-tooltip-trigger" data-tooltip-pos="top">
      Smart
      <span class="smart-tooltip">Position adaptative</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, rgb(99, 102, 241), rgb(139, 92, 246));
   color: rgb(255, 255, 255);
   border-width: medium;
   border-style: none;
   border-color: currentcolor;
   border-image: initial;
   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: rgb(30, 27, 75);
   color: rgb(224, 231, 255);
   font-size: 0.78rem;
   font-weight: 500;
   border-radius: 8px;
   white-space: nowrap;
   pointer-events: none;
   opacity: 0;
   transition: opacity 0.2s, transform 0.2s;
   z-index: 100;
   box-shadow: rgba(0, 0, 0, 0.35) 0px 8px 24px;
   }

.anchor-tooltip-trigger .smart-tooltip::after  {
   content: "";
   position: absolute;
   left: 50%;
   transform: translateX(-50%);
   top: 100%;
   border-width: 6px;
   border-style: solid;
   border-color: rgb(30, 27, 75) transparent transparent;
   border-image: initial;
   }

.anchor-tooltip-trigger .smart-tooltip.flipped-bottom  {
   bottom: auto;
   top: calc(100% + 10px);
   }

.anchor-tooltip-trigger .smart-tooltip.flipped-bottom::after  {
   top: auto;
   bottom: 100%;
   border-top-color: transparent;
   border-bottom-color: rgb(30, 27, 75);
   }

.anchor-tooltip-trigger .smart-tooltip.flipped-left  {
   left: auto;
   right: 0px;
   transform: none;
   }

.anchor-tooltip-trigger .smart-tooltip.flipped-left::after  {
   left: auto;
   right: 14px;
   transform: none;
   }

.anchor-tooltip-trigger .smart-tooltip.flipped-right  {
   left: 0px;
   right: auto;
   transform: none;
   }

.anchor-tooltip-trigger .smart-tooltip.flipped-right::after  {
   left: 14px;
   right: auto;
   transform: none;
   }

.anchor-tooltip-trigger:hover .smart-tooltip, .anchor-tooltip-trigger:focus .smart-tooltip  {
   opacity: 1;
   }
JavaScript (fx-0475)
// Effet CSS pur — pas de JavaScript nécessaire

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.