Tooltip Arrow
Tooltip positioned above its trigger, with a CSS triangle arrow and a rise animation — 100% CSS, zero JS.
You'll get access to the interactive demo with a free account.
This effect is part of Effect.Labs — 811 vanilla effects, some free, some premium. Navigation has 36 effects, including 4 free. Explore the category →
3 usage examples



How it works
The tooltip is a span.tooltip-arrow direct child of the trigger button. It is positioned with position: absolute, anchored via bottom: calc(100% + 12px) (above the parent) and horizontally centered by left: 50%; transform: translateX(-50%). No JS, no DOM measurement at runtime — positioning is entirely declarative.
The downward-pointing arrow is generated by the ::after pseudo-element using the CSS border trick: a zero-size box whose four colored borders form a triangle. border-width: 8px sets the tip size; border-color: #8b5cf6 transparent transparent colors only the top border, leaving the other three invisible — which draws the triangle.
Visibility is driven by the opacity: 0 / visibility: hidden pair at rest and opacity: 1 / visibility: visible via the .tooltip-trigger:hover .tooltip-arrow selector. The visibility property is needed alongside opacity: without it, the invisible span would intercept mouse events from elements below.
The rise animation is produced by transform: translateX(-50%) translateY(-5px) on hover — the tooltip moves up 5 px from its resting position. The transition opacity .3s, visibility .3s, transform .3s orchestrates all three properties in parallel. Note: the code contains no prefers-reduced-motion handling — the animation plays regardless of the user's OS motion preference.
Accessibility
- prefers-reduced-motion absent: the code has no
@media (prefers-reduced-motion: reduce)rule. The rise animation and opacity/transform transitions play even when the user has enabled reduced motion in their OS. Fix: add@media (prefers-reduced-motion: reduce) { .tooltip-arrow { transition: opacity .15s, visibility .15s; transform: translateX(-50%) !important; } }. - Tooltip/trigger relationship not declared in ARIA: the
spanhas norole="tooltip"and the button has noaria-describedbypointing to it. Screen readers will not read the tooltip on hover. - Tooltip not accessible by keyboard focus: the
:hoverselector does not activate on focus. Add.tooltip-trigger:focus-visible .tooltip-arrow { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(-5px); }. - Text contrast: white
#fffon indigo gradient#6366f1→ ratio ≈ 5.5:1, WCAG AA met. - The trigger is a native HTML
<button>: keyboard-focusable, activatable with Enter/Space, visible in the accessibility tree without extra markup.
Browser compatibility
Relies on position: absolute, ::after pseudo-element, transform, transition, opacity and visibility — no JS, no modern API required.
Without <code>transition</code> support (IE 9 and earlier), the tooltip appears and disappears instantly without animation — still functional. On mobile, where the <code>hover</code> event does not exist, the tooltip stays hidden: provide a tap trigger or replace with a JS-driven component.
The code
HTML structure to paste into your page (CSS + JS available with a premium account):
<div class="demo-preview">
<div class="component-demo">
<button class="tooltip-trigger">
Trigger label
<span class="tooltip-arrow">Tooltip text</span>
</button>
</div>
</div>
Full HTML + CSS + JS, copy-paste ready — with hundreds of premium effects.
Customize
Options passed to the API or data-* attributes:
| Option / property | Default | Effect |
|---|---|---|
| .tooltip-arrow background | linear-gradient(135deg, #6366f1, #8b5cf6) | Tooltip background gradient. Swap in your brand palette — the gradient end color must match the border-color value in ::after. |
| .tooltip-arrow::after border-color | #8b5cf6 transparent transparent | Arrow triangle color. Must match the gradient end color of the tooltip for a seamless look. |
| bottom: calc(100% + 12px) | 12px | Vertical gap between the trigger top and the tooltip bottom. Increase for more breathing room, decrease for dense UIs. |
| .tooltip-arrow padding | 10px 18px | Inner spacing of the tooltip. Reduce to 6px 12px for compact toolbars, increase for long help text. |
| .tooltip-arrow font-size | 0.85rem | Text size. 0.75rem for dense toolbars, 0.9rem for help tooltips meant to be read carefully. |
| .tooltip-arrow border-radius | 8px | Corner rounding. 4px for a utilitarian style, 12px for a softer design. |
| .tooltip-arrow::after border-width | 8px | Arrow tip size in pixels. 5px for a subtle pointer, 12px for a prominent arrow. |
| transition duration | .3s | Duration of opacity/visibility/transform transitions. Reduce to .15s for a snappier feel, increase to .5s for a more elegant reveal. |
FAQ
left:50%/translateX(-50%)) with no collision detection. For elements near the edge, manually adjust left with a clamped negative offset, or use a contextual positioning library like Floating UI that handles boundary awareness automatically.:hover). Add this selector for keyboard focus: .tooltip-trigger:focus-visible .tooltip-arrow { opacity: 1; visibility: visible; transform: translateX(-50%) translateY(-5px); }. Also add role="tooltip" to the span and aria-describedby on the button for screen reader support.bottom: calc(100% + 12px) with top: calc(100% + 12px) and change border-color to transparent transparent #8b5cf6 (upward triangle). For left/right: use right: calc(100% + 12px) or left: calc(100% + 12px) with transform: translateY(-50%) and the matching border-color directions.