Tooltip

A contextual hint that appears on hover (desktop) or long-press (mobile). Ink background, paper text, 10px radius. Used sparingly — only for information that doesn't fit inline and isn't critical enough for a label. Max 200px wide, max 2 lines.

Variants

Cap applies after 15 expenses/month on the free tier
Settle flow sends a UPI request — no bank login needed
Previous month's balance
Shows your share of all unsettled expenses

Four placements: top (default), bottom, left, right. All share the same visual treatment. Choose placement based on available space — the tooltip should never clip off-screen or obscure the trigger element.

Hover over the buttons above to see each placement.

Sizes

Default
Hover to reveal context — max 200px wide, 13px body
Short
Shorter hint
PropertyValueNotes
Max width200pxText wraps within this. Never wider.
Border radius10pxBetween card (14px) and tag (pill)
Padding8px 12pxCompact — tooltip should feel like a label, not a card
Font size13pxOne step below body — contextual, not primary
Max lines2If you need more, the hint needs to be a label or help text
Offset from trigger8pxConsistent gap on all placements

States

Tip appears on hover
Default / hidden
Cap meter tracks your monthly expense count
Visible
StateOpacityTransformTrigger
Hidden0scale(0.96)Default
Visible1scale(1)Hover (desktop), long-press 500ms (mobile), focus-within
Exit0 → via transitionscale(1) → scale(0.96)Pointer leaves, focus leaves, touch ends

On dark surfaces

UPI request goes directly to Aarav's PhonePe

Tooltip appearance is the same on dark surfaces — ink background with paper text is self-contained and readable against both light and dark canvas. No dark-mode variant is needed. The shadow intensifies naturally because --shadow-md uses the dark-mode override from tokens.

Anatomy

Contextual hint text here
Bubble (ink bg, paper text, 10px radius)
Arrow (5px, matches bg color)
Trigger element
PartElementNotes
Wrapper.tooltip-wrapRelative-positioned container. The trigger and tooltip are both children of this wrapper.
TriggerAny focusable elementUsually a button, icon button, or element with tabindex="0". Provides aria-describedby pointing to the tooltip.
Bubble.tooltipAbsolute positioned. Ink bg, paper text. Opacity + scale transition on show/hide.
Arrow.tooltip::afterCSS triangle using borders. Color must match the bubble background.

Usage

Do Use tooltips sparingly — only for non-critical information that won't fit inline. Good candidates: icon buttons that need a label, abbreviations, numbers that need a brief explanation (e.g. the cap meter percentage). One sentence maximum.
Don't Don't use tooltips for critical information — if someone needs to see it to complete a task, it should be visible without hovering. Don't put interactive content (links, buttons) inside a tooltip. Don't use tooltips on mobile-only surfaces.
Do Choose placement that avoids screen edges. Default to top, switch to bottom if there's no space above. On mobile, long-press (500ms) reveals the tooltip; a tap elsewhere dismisses it.
Don't Don't show more than one tooltip at a time. Don't use a tooltip for text that's already obvious from context. Don't delay the tooltip more than 200ms — it should feel immediate on desktop.

Accessibility

ARIA Give the tooltip a unique id and add aria-describedby="that-id" to the trigger element. Add role="tooltip" to the bubble. Screen readers will announce the tooltip content after the trigger's own label.
Keyboard The tooltip appears when its trigger receives focus (via :focus-within on the wrapper). It dismisses when focus leaves. This means keyboard users always have access — no mouse required. Never remove the focus trigger.
Mobile / touch Hover doesn't exist on touch devices. Implement a 500ms long-press to reveal the tooltip, with a tap-outside dismissal. Ensure the touch target for the trigger is at least 44×44px.
Reduced motion With prefers-reduced-motion: reduce, skip the scale animation — only fade (opacity). The transition duration remains the same; only the transform is removed.

Code

HTML

<!-- Top placement (default) -->
<div class="tooltip-wrap">
  <button aria-describedby="tip-cap">Cap meter</button>
  <div class="tooltip tooltip-top" id="tip-cap" role="tooltip">
    Resets on the 1st of each month
  </div>
</div>

<!-- Right placement -->
<div class="tooltip-wrap">
  <button aria-describedby="tip-upi">Send request</button>
  <div class="tooltip tooltip-right" id="tip-upi" role="tooltip">
    UPI request — no bank login needed
  </div>
</div>

JavaScript — mobile long-press

// Long-press to reveal tooltip on mobile
document.querySelectorAll('.tooltip-wrap').forEach(wrap => {
  let timer;
  wrap.addEventListener('touchstart', () => {
    timer = setTimeout(() => wrap.classList.add('lp-active'), 500);
  }, { passive: true });
  wrap.addEventListener('touchend',   () => clearTimeout(timer));
  wrap.addEventListener('touchmove',  () => clearTimeout(timer));
  document.addEventListener('touchstart', e => {
    if (!wrap.contains(e.target)) wrap.classList.remove('lp-active');
  });
});

Design tokens used

TokenValueRole
--ink#171513Bubble background + arrow
--s-paper#FBF8F0Bubble text
--shadow-md0 8px 24px -8px rgba(23,21,19,.12)Bubble elevation
--d-fast140msShow/hide transition
--ease-outcubic-bezier(0.23,1,0.32,1)Scale + opacity easing
--z-tooltip300Stack order — above toasts