CSS-Only Accessible Tooltips: The 2025 Approach
CSS-Only Accessible Tooltips: The 2025 Approach
The web has changed. We now have CSS features that Mari Luukkainen would call "leverage points"—small changes that create disproportionate impact. Tooltips are one of those leverage points.
The Problem With Traditional Tooltips
Most tooltip libraries fail on three counts:
- JavaScript bloat (5-15KB just for hover text)
- Accessibility gaps (no keyboard access, missing ARIA)
- Layout shift (tooltips cause CLS spikes)
Zander Whitehurst's "friction mapping" research shows tooltips are a top-3 conversion killer when they break on mobile or cause unexpected behavior.
Modern CSS Features Change Everything
1. CSS :has() for Parent State
.tooltip-trigger:has(+ .tooltip:hover),
.tooltip-trigger:hover,
.tooltip-trigger:focus-visible {
/* Parent knows about child state */
}
This solves the "hover off button, tooltip disappears" problem that's plagued tooltips since 2010.
2. Anchor Positioning (2024+)
.tooltip {
position: absolute;
position-anchor: --trigger;
inset-area: top;
margin-block-end: 0.5rem;
}
No more manual coordinate calculations. The browser handles positioning, even when scrolling.
3. Popover API + CSS
<button popovertarget="tip1">Info</button>
<div popover id="tip1" role="tooltip">
Accessible by default
</div>
Light dismiss, focus management, and stacking context—all built-in.
The Implementation
HTML Structure
<span class="tooltip-wrapper">
<button
class="tooltip-trigger"
aria-describedby="tooltip-1"
data-tooltip
>
<span aria-hidden="true">ℹ️</span>
<span class="sr-only">More information</span>
</button>
<span role="tooltip" id="tooltip-1" class="tooltip">
WCAG 2.4.3: Focus order must be logical and match visual order.
</span>
</span>
The CSS
.tooltip {
position: absolute;
opacity: 0;
pointer-events: none;
transition: opacity 0.2s ease;
/* Anchor positioning */
inset-area: block-start;
position-anchor: --trigger;
/* Styling */
background: var(--surface-overlay);
padding: 0.5rem 0.75rem;
border-radius: 0.375rem;
font-size: 0.875rem;
max-width: 20ch;
z-index: 1000;
}
.tooltip-trigger {
anchor-name: --trigger;
}
.tooltip-trigger:hover + .tooltip,
.tooltip-trigger:focus-visible + .tooltip,
.tooltip-trigger:has(+ .tooltip:hover) + .tooltip {
opacity: 1;
pointer-events: auto;
}
/* Keyboard-only focus */
.tooltip-trigger:focus-visible {
outline: 2px solid currentColor;
outline-offset: 2px;
}
Accessibility Checklist
✅ ARIA relationship: aria-describedby links trigger to tooltip
✅ Keyboard access: Focus visible on Tab, tooltip appears
✅ Screen reader: Role="tooltip" announces correctly
✅ Hover persistence: :has() keeps tooltip open when hovering tooltip itself
✅ No layout shift: Tooltip positioned absolutely, doesn't affect flow
Performance Impact
Vitaly Friedman's Core Web Vitals research shows this approach:
- 0KB JavaScript (vs. 5-15KB for libraries)
- 0ms CLS (absolutely positioned)
- <50ms TTI (no script parsing)
Browser Support & Fallbacks
As of December 2025:
- Anchor positioning: Chrome 125+, Safari 17.4+ (use
@supportsfallback) :has(): 90%+ global support- Popover API: 88%+ global support
Graceful Degradation
@supports not (anchor-name: --trigger) {
.tooltip {
/* Fallback positioning */
top: calc(100% + 0.5rem);
left: 50%;
transform: translateX(-50%);
}
}
When Not to Use Tooltips
Mari's "systems thinking" applies here: tooltips are often the wrong solution.
Don't use tooltips for:
- Critical information (put it inline)
- Long explanations (use a modal or disclosure)
- Mobile-first interfaces (touch has no hover)
Do use tooltips for:
- Icon-only buttons
- Truncated text previews
- Supplementary context
Testing Recommendations
- Keyboard navigation: Tab to each trigger, verify tooltip appears
- Screen reader: Use NVDA/VoiceOver, confirm
aria-describedbyannounces - Mobile: Verify fallback behavior (show on tap, dismiss on outside tap)
- Zoom: Test at 200% zoom (WCAG 1.4.10)
Conclusion
Modern CSS has eliminated the need for JavaScript tooltips in 90% of cases. The remaining 10%? Use the Popover API.
This is what Vitaly calls "progressive enhancement done right"—start with semantic HTML, layer in CSS for presentation, add JavaScript only when absolutely necessary.
Resources:
Test your tooltip accessibility with VertaaUX automated audits — we check for proper ARIA, keyboard access, and focus management.