Guide cue (one-shot onboarding tooltip)

One-shot tooltip-style component with arrow + title + body + optional CTA, surfaces a single feature discovery, persists per-user so it never re-shows after dismissal. Modeled after MongoDB LeafyGreen GuideCue.

Component — Guide cue

*tatus* v0.1.0 — Draft. Sits between info-sprinkle (lightweight inline help) and callout-card (full surface nudge) on the affordance weight spectrum.

R1 — Anatomy

  • Arrow indicator pointing at the highlighted feature.
  • Title (1 short sentence).
  • Body (1–2 sentences).
  • Dismiss (×) topright; keyboardaccessible.
  • Optional CTA (verb, 1–3 words: Try it, Show me).

R2 — Positioning

  • Uses Floating UI semantics: choose side (toprightbottom/left) based on available viewport space.
  • Arrow always points at the anchor element's nearest edge.
  • 8px gap between arrow tip and anchor element.

R3 — Trigger

  • *ountontrigger* cue appears X ms after the anchor element mounts (default 800ms; configurable).
  • *how()* imperative API to surface cue on demand (rare; usually mountontrigger).
  • Does NOT trigger on hover/focus (that's info-sprinkle's job).

R4 — Persistence

  • Each cue carries a stable cue_id.
  • On dismiss (× or CTA click) the cue NEVER re-shows for that user + cue_id pair.
  • Storage:
    • Authenticated: persisted via Koder ID profile (cross-device).
    • Unauthenticated: localStorage with key koder.guide_cue.{cue_id}=dismissed.
  • Resetting cues (admin / dev mode): koder.guide_cue.* localStorage clearance + Koder ID profile reset (consumer feature, not KDS-managed).

R5 — Accessibility

  • Container: role="dialog" with aria-modal="false" (does NOT trap focus; user can ignore).
  • aria-labelledby points at title; aria-describedby points at body.
  • Initial focus does NOT move into the cue — the anchor element keeps focus to avoid hijack.
  • Tab from anchor enters the cue (title → body → CTA → dismiss).
  • Esc dismisses (counts as a dismissal — never re-shows).

R6 — Multiple cues on a page

  • One visible cue per page at a time.
  • If multiple cues are eligible to show, queue them in mount order.
  • After a cue dismisses, wait 500ms before showing the next (prevents stacking).

R7 — Animation

  • Default: fade + 4px slidein toward the anchor, 200ms easeout.
  • prefers-reduced-motion: reduce: instant appear, no slide.

R8 — OUIA

Per specs/testing/ouia-test-hooks.kmd:

  • data-ouia-component-type="GuideCue"
  • data-ouia-component-id="<cue-id>"
  • data-ouia-safe="true" only when visible and animation complete.

Não-escopo

  • Tour sequencing (multi-cue narrative with Next/Previous) — separate ticket if demand surfaces.
  • Server-side analytics on cue impression / dismiss (consumer concern).

Source: ../home/koder/dev/koder/meta/docs/stack/specs/components/guide-cue.kmd