A compact attention strip with neutral, info, warning, or danger intent, optional description, and an optional leading icon.
label + label size booleans into a single labelSize enum, and expand type into a proper 4-value intent enum (info / success / warning / error). Add a leading-icon slot, a trailing action slot, and Pressed / Disabled states.Appears beneath form fields, inside modals, or between screen sections — to clarify what happens next, flag a soft warning, or offer supplemental instructions that don't rise to Alert-level severity.
main/contextual-help/color/{default|info}/*). Missing: a leading icon slot and a trailing action slot; both are standard in the industry pattern.label=yes/no + label size=small/default/no. Three Cartesian cells are invalid. The type property is also a 2-value stub of what should be a proper intent enum.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default (neutral) | Yes | Yes | type=default | Grey-blue bg #F6F9FD, border #E5EBF4, label #445C85, description #6780A9. Ambient hints. |
| Information | Yes | Yes | type=information | Light blue bg #E5F1FF, border #D2E5FF, label #072592. Helpful context. |
| Warning | N/A | N/A | — (missing) | No warning intent. Consumers reach for Alert component instead, which is heavier. |
| Error | N/A | N/A | — (missing) | No error intent. Same workaround as Warning. |
| Pressed | N/A | N/A | — (missing) | If the callout is tappable (e.g. opens a sheet with more info), there's no pressed state to reflect that affordance. |
| Disabled | N/A | N/A | — (missing) | No disabled state. Parent form disabled context is not reflected in the callout. |
- Component name "Contextual Help" is internal jargon. Industry-standard term for this shape is Callout — used by Atlassian, GitHub, Notion, and Stripe. The current name doesn't describe the anatomy and overlaps conceptually with Tooltip. Rename the component to make its purpose self-evident in picker menus and code. C1 · Layer Structure & Naming
- Redundant label axes.
label=yes/noandlabel size=small/default/noencode overlapping information. Whenlabel=no,label sizeis forced tono; two of the nine Cartesian cells (no/small,no/default) are invalid and unused. Collapse to a singlelabelSize: none | small | defaultenum. C2 · Variant & Property Naming -
type=default | informationis a two-value stub. Real-world callouts need Info / Success / Warning / Error. Today consumers escalate to Alert for Warning or Error because no Warning or Error callout exists — Alert over-weights the UI for soft messages. Expand to a 4-valueintentenum. C2 · Variant & Property Naming - No leading icon slot. Every mature callout in the industry (Material, HIG, GitHub, Notion, Stripe) leads with an intent-bound icon — info, check, warning triangle, error circle. This component relies on colour alone, which fails WCAG 1.4.1 (Use of Color). C4 · Native Mappability
- No interaction states. Callouts frequently host a trailing "Learn more" link or open a sheet on tap. Without Pressed / Focused / Disabled, the Figma source can't express either affordance and consumers nest a Button (too heavy) or link the whole container (no pressed feedback). C5 · Interaction State Coverage
- No trailing action slot. A named trailing slot — for a "Learn more" TextButton or a dismiss X — is a canonical callout affordance. Consumers currently detach the instance or append a sibling, both of which break DS governance. C4 · Native Mappability
- Icon for
type=informationnot wired. Figma ships an Information variant with no actual information icon — only a blue tint. If the plan is to carry intent via colour, this is the C6 gap to close: add a vector icon instance bound tomain/callout/info/icon. C6 · Asset & Icon Quality - Code Connect mappings not registered. Blocked behind the rename, property-schema collapse, intent-enum expansion, icon slot, and interaction states. No native component file exists yet. C7 · Code Connect Linkability
- Rename Contextual Help → Callout. Industry-standard term (Atlassian, GitHub, Notion, Stripe). Describes the anatomy — an inline informational block — and doesn't collide with Tooltip, Alert, or Subtext Message. Also rename the token namespace from
main/contextual-help/*tomain/callout/*, and the component file toEBCallout.swift/EBCallout.kt. Rename - Collapse
label+label sizeinto onelabelSizeenum. Replace the two properties with a singlelabelSize: none | small | defaultenum. Three Cartesian cells (no/small,no/default, plus the implicit invalid combinations) disappear, and consumers pick a size directly — no more "turn label on then pick a size" two-step. Property - Expand
typeinto a 4-valueintentenum. Replacetype=default | informationwithintent: .info | .success | .warning | .error. Add the corresponding token groups (main/callout/success/*,main/callout/warning/*,main/callout/error/*) and default leading icons per intent. Pushes soft-severity messages off Alert and into Callout where they belong. Property - Add a leading-icon slot with per-intent defaults. Every callout intent ships a default icon (info-circle, check-circle, warning-triangle, error-circle) that consumers can override or hide. Bind to
main/callout/{intent}/icon. Closes the colour-only accessibility gap and matches Material, HIG, GitHub, and Notion conventions. Slot - Add a trailing action slot. Adopt Figma Slots to expose
#trailing-actionfor a single TextButton (e.g. "Learn more") or a close X icon. Consumers instance-swap a Text Button into the slot; the callout handles spacing and vertical alignment. Slot - Add Pressed and Disabled states. When the entire callout is tappable (opens a sheet with more detail), it needs a Pressed state with a slightly darker bg token. When a parent form is disabled, the callout needs a matched Disabled state (muted label + description + 0.6 opacity border). State
- Rename
main/contextual-help/color/info/*→main/callout/info/*. Token names follow the component name. Rename the whole group at once when the component rename lands; defer until native handoff so consuming files don't thrash. Token - Document the Callout vs Alert vs Subtext Message vs Tooltip decision tree. Designers conflate these four because the naming overlaps. Publish a one-pager: Subtext (field helper), Callout (inline block, soft intent), Alert (page-level banner, hard intent), Tooltip (hover/press popover). No Figma change — a usage doc on the guide page. Docs
Compact attention strip. Flip Type / Label / Label size / Description to walk through every variant.
All colors bound to main/contextual-help/color/* tokens (target after rename: main/callout/*). No appearance modes. No Pressed or Disabled state defined.
| Role | Token | TOKEN | VALUE |
|---|---|---|---|
| Default | bg | main/contextual-help/color/default/bg | #F6F9FD |
| Default | border | main/contextual-help/color/default/border | #E5EBF4 |
| Default | label | main/contextual-help/color/default/label | #445C85 |
| Default | description | main/contextual-help/color/default/description | #6780A9 |
| Information | bg | main/contextual-help/color/info/bg | #E5F1FF |
| Information | border | main/contextual-help/color/info/border | #D2E5FF |
| Information | label | main/contextual-help/color/info/label | #072592 |
| Information | description | main/contextual-help/color/info/description | #6780A9 |
| Success | — | — (missing) | — |
| Warning | — | — (missing) | — |
| Error | — | — (missing) | — |
| Pressed | — | — (missing) | — |
| Disabled | — | — (missing) | — |
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:feedback:1.0.0") }
Import
import EastBlueDS // SwiftUI import com.eastblue.ds.feedback.* // Compose
Package not yet published. Names reflect the proposed rename (EBCallout, not EBContextualHelp).
Assumes the recommended architecture: single labelSize enum (replacing label + label size), full 4-value intent enum (replacing type), plus an optional leading icon and trailing action slot.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| label (yes/no) + label size (small/default/no) | labelSize: EBCalloutLabelSize = .none | labelSize: EBCalloutLabelSize = None |
| Label text | title: String? | title: String? = null |
| description (yes/no) | description: String? | description: String? = null |
| type=default | information | .ebIntent(.info / .success / .warning / .error) | intent: EBCalloutIntent |
| — (missing) | leadingIcon: Image? | leadingIcon: @Composable (() -> Unit)? |
| — (missing) | trailingAction: (() -> some View)? | trailingAction: @Composable (() -> Unit)? |
| — (missing) | .disabled(true) | enabled: Boolean = true |
EBCallout( title: "Add title here", description: "This is a short helpful context message for the user." ) .ebIntent(.info) .ebLabelSize(.default)
EBCallout( title = "Add title here", description = "This is a short helpful context message for the user.", intent = EBCalloutIntent.Info, labelSize = EBCalloutLabelSize.Default )
EBCallout( title: "Verify your number", description: "We'll send a one-time code to confirm this device." ) .ebIntent(.info) .ebTrailingAction { EBTextButton("Learn more") { showSheet = true } }
EBCallout( title = "Verify your number", description = "We'll send a one-time code to confirm this device.", intent = EBCalloutIntent.Info, trailingAction = { EBTextButton("Learn more", onClick = { showSheet = true }) } )
EBCallout( description: "Transfers above ₱50,000 require ID verification." ) .ebIntent(.warning) .ebLabelSize(.none)
EBCallout( description = "Transfers above ₱50,000 require ID verification.", intent = EBCalloutIntent.Warning, labelSize = EBCalloutLabelSize.None )
| Requirement | iOS | Android |
|---|---|---|
| Don't rely on colour alone | Pair intent with a leading icon (info / warning / error). WCAG 1.4.1. | Same — both icon and color must carry intent. |
| Semantic grouping | Wrap label + description in an accessibilityElement(children: .combine) so VoiceOver reads them as one note. | Use Modifier.semantics(mergeDescendants = true). |
| Live-region announce | If the callout appears after a user action, post a UIAccessibility.Notification.announcement with its text. | Use liveRegion = LiveRegionMode.Polite when the callout mounts. |
| Tappable callout target | Minimum 44 × 44 pt if the whole container is tappable. | Minimum 48 × 48 dp same. |
| Dynamic type / font scaling | Label and description both scale with Dynamic Type; don't hard-lock line-height to 16/20. | Use sp units and respect fontScale. |
Do
Use Callout for soft, inline guidance — a flow hint, a policy reminder, a "this is what happens next" note.
Don't
Use Callout for page-level critical messages — reach for Alert when the severity warrants a dismissible banner.
Do
Pair intent colour with a leading icon. Every intent ships a default (info-circle, check-circle, warning-triangle, error-circle).
Don't
Rely on colour alone to communicate severity — fails WCAG 1.4.1 and breaks for colour-blind users.
Do
Put a single TextButton in the trailing action slot when the callout opens a sheet or links to docs.
Don't
Nest a full filled Button — it overweights the callout. If the call to action is primary, it belongs outside the callout.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Requires Rework | Component name "Contextual Help" is internal jargon. Rename to Callout. Inner layers are reasonable. |
| C2 | Variant & Property Naming | Requires Rework | Redundant label + label size encode one concept; type is a 2-value stub of a 4-value intent. |
| C3 | Token Coverage | Ready | All bg / border / label / description bound to main/contextual-help/color/*. Spacing uses space/*. Rename namespace alongside the component rename. |
| C4 | Native Mappability | Requires Rework | No leading-icon slot, no trailing-action slot, no system primitive match on either platform — custom EBCallout with intent enum required. |
| C5 | Interaction State Coverage | Requires Rework | No Pressed, Focused, or Disabled states. Tappable callouts and disabled-form contexts can't be expressed. |
| C6 | Asset & Icon Quality | Requires Rework | No icons shipped today — Information variant carries intent via colour alone. Add vector Icon instances per intent. |
| C7 | Code Connect Linkability | Not Mapped | Blocked by rename + property schema collapse + intent expansion + slot additions. |
| Aspect | Status | Notes |
|---|---|---|
| Component name | Requires Rework | Rename to Callout before Code Connect — otherwise the native file EBCallout.swift won't match the Figma name Contextual Help. |
| Property naming | Requires Rework | Collapse label+label size and expand type. |
| Slot inference | Requires Rework | Add #leading-icon and #trailing-action slots. |
| State coverage | Requires Rework | Add Pressed and Disabled states. |
| Native component file | Not Mapped | Planned: EBCallout.swift / EBCallout.kt. |
4 axes: Label (yes/no) × Label Size (small / default / no) × Description (yes/no) × Type (default / information). Cartesian is constrained — of the 24 combinations, only 8 are shipped as valid variants.
| Label | Label Size | Description | Type | Node ID | Dimensions |
|---|---|---|---|---|---|
| yes | small | yes | default | 23:179896 | 336 × 98 |
| yes | small | yes | information | 23:179899 | 336 × 98 |
| yes | default | yes | default | 23:179902 | 336 × 102 |
| yes | default | yes | information | 23:179905 | 336 × 102 |
| no | no | yes | default | 23:179908 | 336 × 84 |
| no | no | yes | information | 23:179910 | 336 × 84 |
| yes | default | no | default | 23:179912 | 336 × 39 |
| yes | default | no | information | 23:179915 | 336 × 39 |
main/callout/* and native files to EBCallout.
Openlabel + label size into a single labelSize: none | small | default enum. Three invalid Cartesian cells removed.
Opentype=default | information with intent: info | success | warning | error. Adds token groups for success / warning / error.
Open