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.
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
| 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 |
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.2stransition 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 textrgb(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-describedbyin the base code: tooltip text is not automatically read by screen readers. For tooltips carrying critical information, addaria-describedby="span-id"on the button and a matchingidon 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+.
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.
<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>
.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;
}
// Effet CSS pur — pas de JavaScript nécessaire
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| 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.