A centered overlay surface used for blocking confirmations and dialogs — header, body, and primary/secondary actions.
Modal component is trying to serve both a general-purpose dialog (default / with icon) and a specialised transaction-receipt layout (transaction_v1 / v2). These are not "variants" of the same thing — they have different information architectures, different tokens, and different native mappings. On top of that, Modal overlaps in name and scope with the existing Overlay component (47:329691), which currently ships the scrim only. Consolidate the family: one canonical Modal that owns the surface + scrim, and a separate Transaction Receipt Card for the receipt layout.Contexts are illustrative. Final screens will reference actual GCash patterns.
transaction_v1 / transaction_v2 use snake_case, 1 - vertical / 2 - horizontal use space-dashed-space. Also duplicates scope with the Overlay component.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Present / dismiss | Yes | Yes | Not annotated | Scale-in + fade animation is implied by pattern but not documented on the component. |
| Tap-outside dismiss | Yes | Yes | Not annotated | Overlay owns the tap-region. Contract should be documented (dismissible vs. modal). |
| CTA resolution | Yes | Yes | Via Button child | 1 / 2-horizontal / 2-vertical layouts maped by cta property. |
| Copy to clipboard (transaction) | Yes | Yes | Icon only, no state | Copy icon is raster, with no pressed / success feedback state defined. |
| Focus trap / a11y | Yes | Yes | Implicit | Focus should be trapped inside the modal while open; restore to trigger on close. |
- Duplicate scope with Overlay component. A separate
Overlayrecord (node47:329691) already owns the scrim primitive; this Modal should compose it, not re-declare the modal surface. Today the two are maintained independently and there's no annotation describing which is canonical. C7 · Code Connect Linkability - Two unrelated layouts compressed into one component.
type=defaultandtype=with iconare general-purpose dialog shapes, whiletype=transaction_v1andtype=transaction_v2are transaction-receipt layouts with their own inner rows and reference-number slot. These are different components masquerading as variants. C4 · Native Mappability - Mixed enum casing within a single property.
typeusesdefault,with icon(space),transaction_v1,transaction_v2(snake_case).ctauses1,1 - vertical,2 - horizontal,2 - vertical(spaces around dashes). Neither is consistent with the rest of the DS. C2 · Variant & Property Naming - Opacity-0 spacer frames instead of gap. Inner layers named
_space_16,_space_12withopacity:0are used to create vertical rhythm. These are non-semantic and don't translate to native auto-layout. Usegapon the parent instead. C1 · Layer Structure & Naming - Raster copy-to-clipboard icon. The transaction variants use PNG assets
shape_half/shape_fullfor the copy icon. Should be a vector icon instance bound tomain/modal-popup/color/icon-copy. C6 · Asset & Icon Quality - Icon-placeholder slot is a grey circle, not an instance swap. The
with iconvariants render a raw#C2C6CFcircle (icon-placeholder). Consumers can't swap in a real icon without detaching. Should be a Slot backed by theIconcomponent. C1 · Layer Structure & Naming - No interaction states on the modal surface. The component ships only a default state — no pressed / dragging state for the CTA group, no loading state for async actions, and no entrance/exit transition annotation. C5 · Interaction State Coverage
- Missing CTA combos. Transaction variants only ship with
cta=1; "with icon" only ships with vertical CTAs. Thectaaxis is not complete across alltypevalues, so designers resort to detaching when they need another arrangement. C2 · Variant & Property Naming - No Code Connect mapping. Blocked on restructure — once the transaction layout is extracted and the enum values are cleaned up, mapping is trivial. C7 · Code Connect Linkability
- Consolidate with Overlay — one canonical Modal family. The current split (Overlay = scrim only, Modal = surface + scrim baked in) duplicates intent. Rename and re-partition into:
Overlay(scrim primitive, already assessed) +Modal(surface composition that references Overlay) +TransactionReceipt(the transaction_v1/v2 layout pulled out as its own card). Document which component owns which concern. Family - Extract transaction_v1 / transaction_v2 as a separate component. Move the receipt layout into a new
Transaction Receiptcomponent (likely a composition of List + Reference Number + Copy action), and drop thetransaction_*values from Modal'stypeenum. Modal keeps only general-dialog variants. Composition - Normalise enum casing. Pick one convention for all multi-word values. Recommendation: single lowercase words separated by dashes —
default,with-icon,cta-single,cta-single-vertical,cta-double-horizontal,cta-double-vertical. Align with Button and other DS components. Rename - Replace opacity-0 spacer frames with auto-layout gap. Remove the
_space_16/_space_12invisible rectangles and setgapon the parent auto-layout frames (usingspace/space-16andspace/space-12tokens). Native translators can then emit properspacingparameters. Property - Convert icon-placeholder into a Figma Slot. Add a named
iconslot to thewith iconvariants backed by the DS Icon component, so designers can instance-swap without detaching. Default to a neutral status icon. Slot - Replace raster copy icon with a vector instance. Swap
shape_half/shape_fullPNGs for the DS vector Copy icon and bind colour tomain/modal-popup/color/icon-copy. While there, add a pressed / copied success state. Asset - Complete the CTA matrix. Ship every
type × ctacombination (or constrain the schema so unsupported combos aren't implied). Currentlydefaultis missing1-vertical,with iconis missing horizontal pairs, and transactions only ship withcta=1. Either fill the gaps or reshape the enum. Property - Add loading and destructive states. Modals routinely host async confirmations — add a
state=loadingvariant (CTA replaced with spinner) and surface destructive-action styling via a boolean or via the child Button's existingisErrorprop. State - Annotate the dismiss contract. Document on the component: entrance / exit animation, focus trap, restore-focus-on-close, ESC-to-dismiss, tap-outside-dismiss. Developers currently have to infer these from adjacent patterns. Docs
- Title + description copy should come from the DS text styles. Title is bound to
Primary/Headlines/Sectionand description toSecondary/Default/Base(BarkAda). Confirm the secondary-font description is intentional — flag as the standing custom-font action item if not. Docs
The general-purpose dialog. Title + description + single CTA on a white card. Use for confirmations, errors, and neutral informational prompts.
Add description here.
White surface card with title, description, and primary CTA over a dimmed scrim.
| Role | Token | Default |
|---|---|---|
| Surface bg | main/modal/surface | #FFFFFF |
| Title label | text/primary/headline | #0A2757 |
| Description | text/primary/body/secondary | #6780A9 |
| Scrim (overlay) | main/overlay/scrim | #020E22 @ 56% |
Dialog that leads with a 92×92 icon to set tone — success, warning, or info. CTAs stack vertically (1 or 2).
Add description here.
White surface with a leading status icon. Icon color inherits from the placed Icon instance (no own token).
| Role | Token | Default |
|---|---|---|
| Surface bg | main/modal/surface | #FFFFFF |
| Title label | text/primary/headline | #0A2757 |
| Description | text/primary/body/secondary | #6780A9 |
| Scrim (overlay) | main/overlay/scrim | #020E22 @ 56% |
Receipt-style dialog used for order, transfer, and subscription summaries. <code>v1</code> stacks label + value per row; <code>v2</code> is horizontal. Both include a reference-number row with copy-to-clipboard. <strong>Recommended for extraction into its own <code>TransactionReceipt</code> component.</strong>
Two-zone surface: a white content card sits on top of a light-blue receipt-offset shelf that holds the reference number row.
| Role | Token | Default |
|---|---|---|
| Outer surface bg | main/modal/transaction/shelf | #F6F9FD |
| Inner content bg | main/modal/transaction/content | #FFFFFF |
| Inner content border | main/modal/transaction/border | #E5EBF4 |
| Title label | text/primary/headline | #0A2757 |
| Description | text/primary/body/secondary | #6780A9 |
| Scrim (overlay) | main/overlay/scrim | #020E22 @ 56% |
| Figma Property | SwiftUI | Compose |
|---|---|---|
type = default | EBModal(title:, description:) | EBModal(title, description) |
type = with icon | EBModal(icon:, title:, description:) | EBModal(icon = { … }, title, description) |
type = transaction_v1 / v2 | Extract → EBTransactionReceipt(layout: .stacked / .inline) | Extract → EBTransactionReceipt(layout = Stacked / Inline) |
cta = 1 | { EBButton(…) } (single trailing closure) | content: { EBButton(…) } |
cta = 1 - vertical | Implicit — single button is always full-width | Implicit — single button is always full-width |
cta = 2 - horizontal | { EBButton(…); EBOutlinedButton(…) } + .ctaLayout(.horizontal) | ctaLayout = CtaLayout.Horizontal |
cta = 2 - vertical | .ctaLayout(.vertical) | ctaLayout = CtaLayout.Vertical |
| (proposed) Dismissible | .interactiveDismissDisabled(!dismissible) | DialogProperties(dismissOnClickOutside = dismissible) |
| (proposed) Loading state | .ebLoading(isLoading) | isLoading = true |
| Requirement | iOS | Android |
|---|---|---|
| Modal trait | Apply .accessibilityAddTraits(.isModal) — VoiceOver trap focus inside. | Use Dialog / AlertDialog — TalkBack treats content as modal by default. |
| Focus management | Focus moves to modal on present; restores to trigger on dismiss. | Focus enters dialog content on show; restored to trigger element on dismiss. |
| Title announcement | Bind the title Text as the accessibilityHeading so it's read first. | Use Modifier.semantics { heading() } on the title; set paneTitle on the surface. |
| Dismiss gesture | ESC / tap-outside / swipe-down should all route through one dismiss handler. | Back gesture + tap-outside configured via DialogProperties(dismissOnBackPress, dismissOnClickOutside). |
| Destructive action | Use role: .destructive on the CTA so VoiceOver announces destructive intent. | Use destructive colour palette; set contentDescription on CTA explicitly. |
| Copy to clipboard | Announce "Copied" via UIAccessibility.post(.announcement, …). | Announce via view.announceForAccessibility("Copied"). |
| Tap target (copy icon) | Wrap 24×24 icon in a ≥44×44 tappable area. | Wrap 24×24 icon in a ≥48×48 dp tappable area. |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Needs Refinement | Opacity-0 _space_* spacer rectangles used instead of gap. Icon-placeholder is a raw circle node, not a named icon slot. |
| C2 | Variant & Property Naming | Needs Refinement | Mixed casing: transaction_v1 (snake) vs 1 - vertical (space-dash-space). CTA matrix is sparse across type values. |
| C3 | Token Coverage | Ready | bg, label, label-primary, border, bg-subtle, icon-copy all bound to main/modal-popup/color/*. Shadow uses Shadow/Depth 0. |
| C4 | Native Mappability | Requires Rework | General-dialog variants map to .sheet / Dialog; transaction variants do not — they need a dedicated Receipt component. Not a single native primitive. |
| C5 | Interaction State Coverage | Needs Refinement | Only default state shipped. No loading, no destructive variant, no copy-success feedback, no present/dismiss transition annotation. |
| C6 | Asset & Icon Quality | Needs Refinement | Copy icon is raster PNG (shape_half, shape_full). Icon-placeholder slot is a hardcoded grey circle instead of a vector icon instance. |
| C7 | Code Connect Linkability | Not Mapped | Blocked on restructure — duplicate scope with Overlay + embedded transaction layout must be resolved before mapping. |
A type × cta matrix would yield 4 × 4 = 16, but only 7 combinations are shipped. The rest are gaps.
| Type | CTA | Node | Dimensions | Notes |
|---|---|---|---|---|
| default | 1 | 18507:71792 | 320 × 212 | Title + description + single CTA. |
| default | 2 - horizontal | 18507:71799 | 320 × 212 | Outlined secondary + filled primary, side-by-side. |
| default | 2 - vertical | 18507:71807 | 320 × 270 | Stacked CTAs, both full-width. |
| with icon | 1 - vertical | 18507:71783 | 320 × 312 | 92×92 icon placeholder + title + desc + single CTA. |
| with icon | 2 - vertical | 18507:71773 | 320 × 370 | 92×92 icon + title + desc + two stacked CTAs. |
| transaction_v1 | 1 | 18507:71706 | 320 × 398 | Receipt layout — rows stacked (label above value). Reference row with copy icon. |
| transaction_v2 | 1 | 18507:71732 | 320 × 404 | Receipt layout — rows inline (label left, value right). Reference row with copy icon. Outer surface uses bg-subtle. |
47:329691) are maintained independently but overlap in intent. Family consolidation required. Open_space_* spacer rectangles + hardcoded icon-placeholder circle. Opentransaction_v1 vs 1 - vertical). Sparse CTA matrix. OpenEBTransactionReceipt. Openshape_half / shape_full PNGs should be a vector icon instance. OpenBarkAda (secondary font). Confirm with design — otherwise covered by the standing custom-font action item. Info