Tooltips

mandatory

Brief contextual label revealing the name or description of a UI element on hover, focus, or long-press. Material parity (`/components/tooltips`). Covers plain tooltips (label-only) and rich tooltips (multi-line + optional action).

Spec — Tooltips

Facet *isual*of Koder Design. Material parity: https://m3.material.io/components/tooltips.

2 variants

Variant Use Content
*lain* Icon button label, short hint 1-3 word label
*ich* Help / explanation, longer context Title + body + optional action

Pick plain by default. Rich tooltip is opt-in for help / docs.

Anatomy (plain)

   ┌──────────┐
   │  Settings │   ← tooltip
   └──────┬───┘
          ▼      ← optional arrow (Material 3 default has no arrow)
        ┌───┐
        │ ⚙ │    ← trigger (icon button)
        └───┘
  • *ackground* inverse-surface (high contrast)
  • *ext* inverse-on-surface, body-small (12/16, weight 400)
  • *adding* 8 px horizontal, 4 px vertical
  • *orner radius* 4 px
  • *levation* 0 (no shadow — relies on contrast)
  • *ax width* 200 dp; wraps at width
  • *rrow*(optional): triangle pointing to trigger, 8 px base

Anatomy (rich)

   ┌─────────────────────────────────────────┐
   │  Voice search                            │  ← title
   │  ────────────────────────────────────── │
   │  Tap the mic to dictate. Hold for       │  ← body
   │  push-to-talk mode.                      │
   │                              [Learn more]│  ← optional action
   └─────────────────────────────────────────┘
                 ▲
              ┌──────┐
              │  🎤  │
              └──────┘
  • *ackground* surface-container (lighter / neutral, not inverse)
  • *itle* title-small (14/20, weight 500), on-surface
  • *ody* body-small (12/16, weight 400), on-surface-variant
  • *ction* text button at bottom-right
  • *adding* 16 px
  • *orner radius* 12 px
  • *levation* 2 dp (shadow + tonal overlay)
  • *ax width* 320 dp

R1 — Trigger interactions

Surface Trigger to show Trigger to hide
Mouse Hover after 500 ms delay Mouse leave OR Esc
Keyboard Focus Blur OR Esc
Touch Long-press (500 ms) Release OR tap elsewhere
Programmatic API call API call

Tooltips MUST be triggered by ALL three input modes — never mouse-only.

R2 — Positioning

Position When
*op*(above trigger) Default — won't conflict with click target
*ottom* When top would overflow viewport
*ight*/ *eft* Compact spaces (toolbar at top of screen)

Position auto-flips at runtime to stay inside viewport (within 8 px of edge).

Offset from trigger: 8 px gap (with or without arrow).

R3 — Plain tooltip rules

  • Single line preferred; wraps to 2 lines max
  • Don't repeat what's already visible (button has text "Save" → no

    tooltip "Save")

  • For icon-only buttons: tooltip is THE label
  • For text buttons: tooltip adds detail only when useful

    ("Save" tooltip "Save to local drive (Ctrl+S)")

R4 — Rich tooltip rules

  • Used for help / explanation, not basic labels
  • Stays visible while user moves into the tooltip area (hover bridge)
  • Single optional action (link, "Learn more", "Try it")
  • Can be dismissed by Esc or clicking outside
  • Useful for: feature discovery, whatthisdoes help, deprecated

    warnings with migration link

R5 — States (trigger)

Tooltip itself doesn't have states — the trigger does:

Trigger state Tooltip behavior
Idle Tooltip hidden
Hover / Focus Show after delay
Active / Pressed Hide tooltip during press (avoids overlap)
Disabled Tooltip still shows (explains WHY disabled is useful)

R6 — Timing

Event Delay
Hover-in show 500 ms (Material 3 default; configurable 300-700 ms)
Hover-out hide 300 ms (gives user time to move to tooltip if rich)
Long-press show 500 ms (system gesture)
Focus show 0 ms (immediate for keyboard)
Esc hide 0 ms (immediate)

R7 — Multiple tooltips on same surface

  • Only ONE tooltip visible at a time
  • Moving to another trigger: previous hides immediately; new one

    shows after its delay

  • Long horizontal toolbar: skip the show-delay AFTER first tooltip

    appears (subsequent hovers within 500 ms are "instant" — feels responsive)

R8 — Accessibility

  • Plain tooltip's content goes into trigger's accessible name via

    aria-label OR aria-describedby pointing to the tooltip element

  • Rich tooltip: role="tooltip" on tooltip container; trigger has

    aria-describedby pointing to it

  • Tooltip is hidden from focus order (cannot be tabbed to) UNLESS

    rich tooltip has an action — then the action is part of focus order while tooltip is open

  • Esc closes any tooltip
  • Screen reader announces tooltip content when trigger receives focus
  • Don't put critical information ONLY in tooltips (must be available

    to all users; tooltip is supplemental)

R9 — Animation

  • *nter* fadein + scale 0.95 → 1 from anchor edge (motionfast,

    ~150 ms)

  • *xit* fadeout (motionfast)
  • *osition flip during scroll* smooth re-anchor
  • Reduced motion: instant in / out; no scale

R10 — Density

Density Plain padding Plain font Rich padding
Compact 6 px / 3 px 11 px 12 px
Default 8 px / 4 px 12 px 16 px
Comfortable 12 px / 6 px 14 px 20 px

R11 — Per-preset variation

Preset Plain Rich
material3 Inverse surface, no arrow Surface-container, 12 px radius, optional action
material2 Dark gray, optional arrow Solid white card with shadow
ios_cupertino Toast-style, light blur Popover style with arrow
gnome Adwaita compact rounded Adwaita popover style
windows_11 Compact pill, no arrow Fluent flyout
brutalist Solid black box, sharp corners, white text Sharp card with 2 px border
terminal_classic (?) suffix expands inline Bracketed help block below trigger

R12 — Forbidden patterns

  • ❌ Tooltip-only labels for critical actions (must have visible

    label or icon recognized by screen readers)

  • ❌ Tooltips longer than 3 lines (use rich tooltip; if rich, escalate

    to inline help or dialog)

  • ❌ Tooltip on disabled element WITHOUT explaining why
  • ❌ Multiple tooltips visible at once (always ≤ 1)
  • ❌ Tooltips with interactive content other than a single action

    (use popover or dialog)

  • ❌ Tooltips that show on click (use popover for click-triggered)
  • ❌ Tooltips that obstruct the trigger element
  • ❌ Tooltips that appear without the user having interacted at all

    (intrusive — use banners for proactive info)

  • ❌ Hoveronly tooltips on touch surfaces (longpress fallback

    mandatory)

  • themes/color-roles.kmdinverse-surface (plain) /

    surface-container (rich)

  • themes/elevation.kmd — rich tooltip 2 dp recipe
  • themes/typography.kmdbody-small plain / title-small rich
  • themes/motion.kmd — fadein / fadeout timing
  • interaction/states.kmd — trigger hover / focused / pressed

    conflicts with tooltip visibility

  • foundations/elements.kmd — Marker family

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/tooltips.kmd