RestructureRequires Rework
Tooltip V2 Component link

A small floating callout anchored to a target with a directional pointer; ships in eight pointer positions.

Unify 3 Tooltip siblings into one Tooltip component; drop the "V2" suffix
This component, Onboarding - Tooltip, and Tooltip Blurred and Transparent model the same primitive with different skins. Merge into one Tooltip with placement: .top | .right | .bottom | .left (one enum, not 4 booleans), appearance: .default | .onboarding | .translucent, hasArrow, hasDismiss, and a content slot. Replace the raster pointer with a vector, the placeholder icon circle with a Figma Slot, and strip the V2 suffix — production component names should never carry a version. Maps cleanly to TipKit / PlainTooltip / RichTooltip on native.
In Context

Tooltips sit over a target element (tab, button, icon, card) with a pointer aimed at the thing they describe.

Wallet Next
Live Preview
Header
Description goes here. This is the second sentence. The third sentence.
Next
Content
header
description
icon
cta
Placement
pointer
DS Health
Reusable
Partial
Covers the main in-product tooltip pattern across onboarding, tips, and nudges. But because 3 siblings exist for 3 skins, consumers have to hunt for the right one — the primitive is fragmented. C1
Self-contained
Warn
Surface, type, and spacing bind to main/nudge/* + space/* tokens. But the pointer arrow is a raster image (4 rotated copies of one shape) and the leading icon is a gray placeholder circle, not a slot. C6
Consistent
Warn
Component is named Tooltip V2 — version suffixes don't belong in production DS names. Pointer direction is 4 separate booleans rather than one enum, letting consumers set nonsensical combinations (e.g. all 4 pointers on). C2
Composable
Warn
No Figma Slot for leading icon or body content. CTAs are baked Button instances that re-implement pill padding rather than composing the Button component consistently across variants (one variant uses px-16 / py-12, another uses px-8 / py-6). C4
Behavior
State iOS Android Figma Property Notes
Show / hide Yes Yes Not annotated Expected: fade + slight scale-in anchored on the pointer side.
Tap close (X) Yes Yes Close layer Dismiss icon is present in markup but not wired to a property or interaction. Contract should be explicit.
Tap outside N/A N/A Not defined Standard tooltip contract — tap-outside dismisses. Should be documented on the component.
Primary CTA Yes Yes CTA=one / two Advances in onboarding or performs the tip's primary action.
Secondary CTA Yes Yes CTA=two "Back" in onboarding; "Learn more" in tips.
Pressed / Focused N/A N/A Not built CTAs and close have no pressed/focused treatment on the tooltip component itself. Inner Button instances handle their own press, but close + the tooltip surface do not.
Open Issues
  • Component is named Tooltip V2. Version suffixes don't belong in production DS component names — they imply a V1 that wasn't removed, and force consumers to choose which version is "right". Rename to Tooltip and delete V1 (or, if V1 is still in use, merge/deprecate before renaming). C2 · Variant & Property Naming
  • Three sibling Tooltip components for one primitive. Tooltip V2 (70:14908), Onboarding - Tooltip (51:17066), and Tooltip Blurred and Transparent (49:335349) all model the same floating popover with different skins. Collapse into one Tooltip with an appearance enum. C1 · Layer Structure & Naming
  • Pointer direction is 4 independent booleans. pointerTop, pointerRight, pointerBottom, pointerLeft — nothing prevents a consumer from enabling two or all four. A single placement: .top | .right | .bottom | .left | .none enum is the correct shape. Maps 1:1 to TipKit .arrowEdge and Compose TooltipAnchorPosition. C2 · Variant & Property Naming
  • Pointer triangle is a raster asset. 4 separate image fills (imgPointer, imgPointer1, imgPointer2, imgPointer3) for what should be one vector shape rotated per edge. Replace with a single vector triangle component; rotation handled by the placement enum. C6 · Asset & Icon Quality
  • Leading icon is a gray placeholder circle. The Icon=yes variant renders a flat #C2C6CF 46 px circle under a "Placeholder" frame — same anti-pattern as Action List / List Item. Replace with a named leading Figma Slot so consumers can drop in an Icon, Avatar, or illustration. C6 · Asset & Icon Quality
  • Close (X) is an image asset, not a DS icon instance. The dismiss control uses imgShapeFull inside a generic "Close" frame rather than an instance of the DS's icon/close. That hides the icon from a11y / token updates and blocks Code Connect from seeing it as a real control. C1 · Layer Structure & Naming
  • CTA Button padding drifts between variants. CTA=one, Header=yes uses px-16 / py-12; CTA=one, Header=no uses px-12 / py-6; CTA=two uses yet another combo. The underlying Button - XSmall instance should be identical across all variants — same size mode, same padding. C4 · Native Mappability
  • No dismiss / show states modeled. Close button exists visually but carries no interaction property; there is no Pressed / Focused state on the dismiss control or on CTAs at the tooltip level. Tooltips also have an implicit "appearing / dismissing" lifecycle that isn't documented. C5 · Interaction State Coverage
  • Code Connect mappings not registered. Blocked on the consolidation + slot adoption + enum conversion. Mapping the current 3-sibling / 4-boolean shape to native would cement the wrong schema. C7 · Code Connect Linkability
Design Recommendations
  • Consolidate the 3 Tooltip siblings into one component. New schema: placement: .top | .right | .bottom | .left (replaces the 4 booleans), appearance: .default | .onboarding | .translucent (replaces the 3 sibling components), hasArrow: Bool, hasDismiss: Bool, cta: .none | .primary | .primaryAndSecondary, plus a leading slot for icon/avatar and a content slot for the body. Replaces today's 8 + ? + ? variants across 3 components with roughly 4 placement × 3 appearance × 3 cta = 36 permutations of one clean schema. Family
  • Rename Tooltip V2Tooltip. Version suffixes shouldn't appear in production DS names. If V1 is still referenced anywhere, migrate its instances first, then delete V1, then drop the suffix. Rename
  • Replace 4 pointer booleans with a single placement enum. Prevents nonsensical states (all 4 pointers on), maps 1:1 to SwiftUI .arrowEdge and Compose TooltipAnchorPosition, and reduces the variant matrix dramatically. Property
  • Replace the raster pointer with a vector triangle. Today there are 4 separate rasters (top/right/bottom/left). One vector shape, rotated per placement, fills the same role with zero asset burden, scales cleanly, and picks up token color updates automatically. Asset
  • Adopt a leading Figma Slot for the icon. Drop the #C2C6CF placeholder circle. Maps to @ViewBuilder (SwiftUI) and a @Composable slot (Compose). Empty slot = no leading. Slot
  • Instance-swap the close button to icon/close. Use the canonical DS close-icon instance rather than an inline imgShapeFull. Gets you token-driven color, a11y labeling, and press-state handling for free. Composition
  • Normalize CTA Button padding. Every tooltip variant uses the same Button size mode (XSmall). Enforce one padding via the Button component itself — don't let the tooltip override. Composition
  • Document the dismiss contract + lifecycle. Add a description on the component: "Tap the close X or tap outside to dismiss, unless hasDismiss = false (force interaction with CTA). Tooltip appears with fade + slight scale from the pointer anchor; dismisses with reverse." Close the gap between designer intent and dev implementation. Docs
  • Add Pressed / Focused on the close control and CTAs. Inner Button instances handle their own states, but the close X does not — it's an image. Once it becomes an icon-button instance, states follow. State
Styles
Tooltip v2 variations
DES DEV

Full-shape onboarding variant. Leading icon placeholder + header + description + primary CTA + close. 359 × 181.

Content
Header
Description
Icon
CTA
Placement
Pointer
Properties
cta one
icon yes
description true
header true
Variant Hero — full content
Colors
Surface #FFFFFF
Border #E5EBF4
Header #0A2757
Description #6780A9
Close icon #0A2757
Primary CTA bg #005CE5
Primary CTA label #FFFFFF
Secondary CTA #005CE5
Layout
Width 296px (max)
Padding 16 horizontal · 12 vertical
Border radius radius/radius-2 (6px)
Border 1px solid #E5EBF4
Gap (header ↔ desc) 4px (space/space-4)
Pointer size 12 × 8 (width × height)
Typography
Header style Primary/Headlines/Block
Header font Proxima Soft Bold · 18 / 23 · +0.25
Description style Secondary/Bold/Caption
Description font BarkAda Semibold · 12 / 18
CTA style Primary/Label/Base
CTA font Proxima Soft Bold · 16 / 16 · +0.25
Colors by State
Role Token Default
Surface main/nudge/color/primary/bg #FFFFFF
Border main/nudge/color/primary/border #E5EBF4
Header label main/nudge/color/primary/label #0A2757
Description main/nudge/color/primary/description #6780A9
Close icon main/nudge/color/primary/icon-close #0A2757
Leading icon placeholder #C2C6CF (not tokenized — placeholder)
CTA primary bg main/button/primary/brand/enabled/bg #005CE5
CTA primary label main/button/primary/brand/enabled/label #FFFFFF
CTA secondary border / label main/button/secondary/brand/enabled/border #005CE5
Pointer triangle raster (4 images)
Installation Planned API

iOS — Swift Package Manager

// In Xcode: File → Add Package Dependencies
"https://github.com/AY-Org/eb-ds-ios"

Android — Gradle (Kotlin DSL)

dependencies {
    implementation("com.eastblue.ds:tooltip:1.0.0")
}
Property Mapping
Figma PropertySwiftUICompose
3 sibling components 1 component: Tooltip EBTooltip
(sibling = appearance) appearance: .default / .onboarding / .translucent .ebAppearance(.default / .onboarding / .translucent)
pointerTop/Right/Bottom/Left: Bool × 4 placement: .top / .right / .bottom / .left / .none arrowEdge: Edge
header: Bool + text baked title: String? title: String?
description: Bool + text baked body: String? (or content slot) body: String?
icon: Bool (gray placeholder) leading (Slot) @ViewBuilder leading
Close image asset (always) hasDismiss: Bool dismissible: Bool
cta: none / one / two cta: .none / .primary(String) / .pair(back, next) primary / secondary: TooltipAction?
outlineButton: Bool (absorbed into cta.pair)
(not modeled) hasArrow: Bool arrow: Bool
(not modeled) onDismiss onDismiss: () -> Void
SwiftUI
ios/Components/Tooltip/EBTooltip.swift
Jetpack Compose
android/components/tooltip/EBTooltip.kt
Usage Snippets Planned API
Usage
// Simple tip — title + body, pointer below
EBTooltip(
    title: "Quick transfers",
    body: "Tap here to send money to recent contacts.",
    placement: .bottom
)
.onDismiss { showTip = false }

// Walkthrough step — two CTAs
EBTooltip(
    title: "Step 2 of 4",
    body: "Review your balance before confirming.",
    placement: .top,
    cta: .pair(back: "Back", next: "Next")
)
.ebAppearance(.onboarding)

// Rich — custom leading slot
EBTooltip(title: "Welcome", body: "Tap a card to begin.") {
    Image(systemName: "sparkles")
}

// iOS 17 + Tips — recommended for onboarding flows
.popoverTip(quickTransferTip)
// Simple tip
EBTooltip(
    title = "Quick transfers",
    body = "Tap here to send money to recent contacts.",
    placement = EBTooltipPlacement.Bottom,
    onDismiss = { showTip = false }
)

// Walkthrough step — two CTAs, onboarding skin
EBTooltip(
    title = "Step 2 of 4",
    body = "Review your balance before confirming.",
    placement = EBTooltipPlacement.Top,
    appearance = EBTooltipAppearance.Onboarding,
    primaryAction = TooltipAction("Next", onClick = { advance() }),
    secondaryAction = TooltipAction("Back", onClick = { rewind() })
)

// Custom leading slot
EBTooltip(
    title = "Welcome",
    body = "Tap a card to begin.",
    leading = { Icon(Icons.Default.AutoAwesome, contentDescription = null) }
)

// Material 3 PlainTooltip equivalent
PlainTooltip { Text("Shortcut") }
Accessibility
RequirementiOSAndroid
Role + focus Announce tooltip as .accessibilityAddTraits(.isModal) when hasDismiss = true — otherwise role .staticText. semantics { role = Role.Popup } on the container; TalkBack focuses it on appear.
Close control Wrap close as a Button with accessibilityLabel "Dismiss tip" and 44×44 hit target. IconButton with contentDescription = "Dismiss tip"; 48×48dp minimum.
Dismiss-outside Respect UIAccessibility.isVoiceOverRunning — do not auto-dismiss tooltips while VO is active. Do not auto-dismiss while TalkBack is active; hold the tooltip until the user explicitly moves on.
Reduce motion Respect UIAccessibility.isReduceMotionEnabled — skip the scale-in animation; fade only. Respect Settings.Global.TRANSITION_ANIMATION_SCALE — fade only when user has motion reduced.
Combined label Read title + body + "Dismiss" as one phrase; avoid reading pointer. Same; set mergeDescendants = true on the container.
Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Requires Rework 3 sibling components for one primitive. Close uses a raw image asset inside a generic frame rather than an icon/close instance.
C2 Variant & Property Naming Requires Rework Version suffix in the component name (Tooltip V2). Pointer direction is 4 booleans instead of one enum.
C3 Token Coverage Ready Surface, border, label, description, and CTA colors bound to main/nudge/* and main/button/* tokens. Spacing via space/*.
C4 Native Mappability Requires Rework Maps cleanly once pointer booleans → placement enum and sibling skins → appearance enum. CTA padding inconsistencies block a 1:1 Button reuse.
C5 Interaction State Coverage Requires Rework No Pressed / Focused on close. No lifecycle (appearing / dismissing) annotated. Close isn't wired to a dismiss property.
C6 Asset & Icon Quality Requires Rework Pointer is 4 raster images (one per edge). Leading icon is a gray placeholder circle. Close is an image asset.
C7 Code Connect Linkability Not Mapped Blocked on consolidation + enum conversion + slot adoption. Mapping today's shape would cement the wrong schema.
Variants Inventory (8 total)

4 booleans and one 3-value enum yield 8 shipped variants out of a 24-cell theoretical matrix (CTA (3) × Icon (2) × Description (2) × Header (2) = 24). Only the 8 meaningful combinations are exposed. Pointer direction is 4 additional booleans outside the variant matrix.

#CTAIconDescriptionHeaderDimensionsNode
1oneyesyesyes359 × 18170:14907
2onenoyesyes359 × 1557977:12260
3noneyesyesyes359 × 13770:14903
4nonenoyesyes359 × 11970:14902
5nonenonoyes359 × 7970:14900
6nonenoyesno359 × 9270:14901
7twonoyesno359 × 13670:14905
8onenoyesno359 × 13670:14906
1.0.0 — April 2026Major
Initial Assessment · node 70:14908
Verdict: Restructure — Consolidate 3 sibling Tooltip components; drop the V2 suffix; replace 4 pointer booleans with one placement enum; replace raster pointer with vector; replace placeholder icon with a Figma Slot. Open
Architecture
C1 — 3 siblings for 1 primitive — Tooltip V2, Onboarding - Tooltip, Tooltip Blurred and Transparent. Merge via appearance enum. Open
C1
C2 — Version suffix in nameTooltip V2 shouldn't exist in production; no V1 surfaces in the file. Open
C2
C2 — Pointer is 4 booleans — Replace pointerTop/Right/Bottom/Left with a single placement enum. Open
C2
C4 — CTA padding drift — Same XSmall Button, three different paddings across variants. Normalize. Open
C4
C5 — No dismiss/focus states — Close is decorative; Pressed / Focused / Lifecycle not modeled. Open
C5
C6 — Raster pointer + placeholder icon — 4 raster images for the pointer; gray circle for the leading icon. Vector + slot. Open
C6
C7 — Code Connect — Blocked on the architectural changes above. Open
C7
Tokens ✓ — Surface / border / label / description / CTA colors all bound to main/nudge/* and main/button/*. Spacing via space/*. Noted
Praise