The receipt dialog — a title, a block of transaction rows, a copyable reference number, and one or two actions.
⤷ TransactionSlot holds real Modal - Transaction Receipt Entry instances rather than hand-drawn rows, so the primitive's renames flowed straight through. Layer names, the property name, both slot names and the button component were all brought into line during this assessment, and nothing is outstanding on the component itself — Code Connect stays open only because the native library does not exist yet. The Needs Refinement badge is about handoff rather than structure: the copy control has no confirmation state and its row falls short of the platform tap-target minimums.Contexts are illustrative. Final screens will reference actual GCash patterns.
Second line of text goes here
Content, TransactionDetails, Container, Reference, ReferenceNumber, ⤷ TransactionSlot and ⤷ ActionSlot all follow the family conventions after this pass, and ActionOrientation matches the property name on Modal.Button_New rather than drawing them. Both slots accept substituted content without detaching.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| ActionOrientation=Vertical | N/A | N/A | 320 × 434 | Two full-width buttons stacked, primary on top. The action area is 156 tall. |
| ActionOrientation=Horizontal | N/A | N/A | 320 × 376 | Two 132-wide buttons side by side, primary on the right. The action area is 98 tall; everything above it is unchanged. |
| ⤷ TransactionSlot | N/A | N/A | 3 × Entry | Holds Modal - Transaction Receipt Entry instances in their Inline layout. Row count is set by the content, not by a variant. |
| Copy | N/A | N/A | 24 × 24 icon | Copies the reference number. The whole reference row is the tap target, not just the icon. |
- Copying the reference number says nothing happened. The copy control has no confirmation state. Nothing in the component tells the user the tap worked — no toast, no icon swap, no label change — so the only feedback is the press itself, which is gone the moment the finger lifts. Copying an identifier is the one action on this screen where a user needs to know it landed, because the result is invisible until they paste it somewhere else. Which feedback to use is a product call; that there is none today is the gap. C5 · Interaction State Coverage
- The transaction rows are real instances.
⤷ TransactionSlotholds three Modal - Transaction Receipt Entry instances. This is what proves the split worked — when the primitive's#nameand#textwere renamed to#labeland#value, all three rows updated without being touched. C4 · Native Mappability - The property name lost its space.
Action OrientationbecameActionOrientation. It was the only variant property in the system with a space in it, and because the property name becomes the native parameter, a space would have forced a rename at the Code Connect boundary. C2 · Variant & Property Naming - Layer names match the rest of the family.
contentbecameContent,transaction-detailsbecameTransactionDetails, andcontainerbecameContainer— the PascalCase convention already settled on Date Picker, Voucher and Countdown. C1 · Layer Structure & Naming - The spacer-shaped name is gone.
reference-offsetbecameReference. The old name described a layout side-effect rather than the content, which is the kind of name that survives into a native layout and confuses whoever reads it next. C1 · Layer Structure & Naming - The reference row says what it is.
For ReceiptbecameReferenceNumber, and its two text layers picked up#labeland#valueto match the primitive's vocabulary even though the row is not an instance of it. The name landed spaced at first and was joined in a later pass, settling the convention againstTransactionDetailsbeside it — the displayed copy still reads "Reference Number", since that is text rather than a layer name. C1 · Layer Structure & Naming - Both modals now use the same button. This component was instancing
Button - Large/Mediumwhile Modal usedButton_New— two different masters with different internal structure in one family.Button_Newwon, so the label is addressable as#labelin both. C4 · Native Mappability - The reference row is a deliberate duplicate, not an oversight. It is styled identically to a Modal - Transaction Receipt Entry in its Inline layout, and the obvious move is to instance it. Confirmed as intentional instead: it does not function as a transaction entry — it sits outside the receipt card on the grey strip and carries a copy control. It is documented here so the parallel styling is maintained on purpose rather than drifting by accident. C4 · Native Mappability
- The title's tracking is a token style.
#titleis the only text in the component at 0 tracking while everything else sits at +0.25. Confirmed as the bound text style rather than a loose override. C3 · Token Coverage - No close control is intentional. There is no X and no scrim in the component. Dismissal runs through the action buttons, and the scrim belongs to Overlay — this component is the card only. C5 · Interaction State Coverage
- Both slots carry the ⤷ prefix.
Transaction SlotandAction Slotbecame⤷ TransactionSlotand⤷ ActionSloton both variants. The prefix is how a slot is told apart from an ordinary frame at a glance, so a half-applied convention was worse than none — the Action Slot rename landed last and closed it. C2 · Variant & Property Naming
- Give the reference row a real tap target height. The copy affordance is a 24×24 icon, below the 44×44 iOS and 48×48 Android minimums. Confirmed that the whole row is the tap target rather than the icon alone, which solves the width — but the row is only 14 tall inside a 40 tall strip, so the height still needs to reach 44. Raising the
Referenceframe to 48 and centring its contents covers both platforms without changing the layout. A11y - Keep the reference row in step with the primitive. It duplicates Modal - Transaction Receipt Entry's Inline styling by hand, deliberately. That means any change to the primitive's type or colour has to be mirrored here, and nothing in Figma will warn you. Worth a note on the component so the next person to touch the primitive knows to check this row. Docs
- Publish the token names once Dev Mode is read. Fills across the component resolve to library variables and the text carries shared library styles — verified on the reference row's own nodes — but the read-only tools return IDs rather than names, so the spec tables carry hex values only. Token
- See siblings: Modal - Transaction Receipt Entry supplies every row in the slot. Modal is the general-purpose dialog this was split away from. Overlay supplies the scrim behind both. Family
Second line of text goes here
Ten roles, identical in both orientations — the action area changes shape, not colour. The last four belong to the appearance/* set, which is the Button’s own variable mode rather than anything this modal sets: the buttons arrive through ⤷ ActionSlot and bring their palette with them. The transaction rows are the same: they are Modal - Transaction Receipt Entry instances, so their label and value colours are that component’s.
| Role | Token | Value |
|---|---|---|
| Surface | bg/color-bg | #F6F9FD |
| Content card | bg/color-bg-main | #FFFFFF |
| Card hairline | border/color-border-weak | #E5EBF4 |
| #title · #description · every #value | text/color-text | #0A2757 |
| Every #label | text/color-text-weaker | #6780A9 |
| Copy icon | border/color-border-primary | #005CE5 |
| ActionSlot · filled fill | appearance/container/fill | #005CE5 |
| ActionSlot · filled label | appearance/label/color | #FFFFFF |
| ActionSlot · outlined stroke | appearance/stroke/color | #005CE5 |
| ActionSlot · outlined label | appearance/label/on-surface/color | #005CE5 |
Second line of text goes here
iOS — Swift Package Manager
.package(url: "https://github.com/AY-Org/eb-ds-ios", from: "1.0.0")
Android — Gradle (Kotlin DSL)
implementation("com.eastblue.ds:modal:1.0.0")
Import
import EastBlueDS import com.eastblue.ds.modal.*
Planned API — the native library does not exist yet. The artifact is the Modal family: Modal, this receipt and <a href="/components/modal-transaction-receipt-entry">Modal - Transaction Receipt Entry</a> all ship in <code>com.eastblue.ds:modal</code> and import <code>com.eastblue.ds.modal.*</code>.
Five properties, in the order the Figma property panel lists them. Three native parameters have no row because Figma has no property behind them — title, description and the reference value are text layers, so a designer sets them by overriding the layer. Each boolean keeps its own parameter alongside the content it gates rather than the content being inferred from a nil, so every Figma property traces 1:1 for Code Connect. hasReferenceNo keeps Figma's spelling for the same reason. actionOrientation is a parameter in both languages rather than a SwiftUI modifier: it changes the layout of the action area, not the component's appearance.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| ActionOrientation — Horizontal, Vertical | actionOrientation: EBActionOrientation | actionOrientation: EBActionOrientation |
| ⤷ ActionSlot (slot) | @ViewBuilder actions: () -> Actions | actions: @Composable () -> Unit |
| hasDescription — true, false | hasDescription: Bool = true | hasDescription: Boolean = true |
| hasReferenceNo — true, false | hasReferenceNo: Bool = true | hasReferenceNo: Boolean = true |
| ⤷ TransactionSlot (slot) | @ViewBuilder entries: () -> Content | entries: @Composable ColumnScope.() -> Unit |
EBTransactionReceiptModal( title: "Payment successful", actionOrientation: .horizontal )
EBTransactionReceiptModal( title = "Payment successful", actionOrientation = EBActionOrientation.Horizontal )
EBTransactionReceiptModal( title: "Payment successful", actionOrientation: .vertical )
EBTransactionReceiptModal( title = "Payment successful", actionOrientation = EBActionOrientation.Vertical )
| Requirement | iOS | Android |
|---|---|---|
| Dialog announced on present | .accessibilityAddTraits(.isModal), focus moves to #title | Dialog with Modifier.semantics { paneTitle = title } |
| Each receipt row reads as one unit | Handled by the entry primitive's .combine | Handled by the entry primitive's mergeDescendants |
| Copy control has a name and a tap target | .accessibilityLabel("Copy reference number"), 44×44 minimum | contentDescription = "Copy reference number", 48dp minimum |
| Copy result is announced | UIAccessibility.post(.announcement, "Copied") | announceForAccessibility("Copied") |
| Primary action is reachable first | Primary precedes secondary in the accessibility order in both orientations | Primary precedes secondary in the traversal order in both orientations |
Do
Use vertical actions when both labels are long or the secondary action matters.
Don't
Don't use horizontal actions with labels that will truncate at 132px.
Do
Fill the slot with entry instances so type and colour stay inherited.
Don't
Don't detach a row to change one value — override the text on the instance.
Do
Keep the reference number in the grey strip where the copy control lives.
Don't
Don't add the reference as a fourth row in the transaction slot; it has different behaviour.
Do
Pair the modal with Overlay for the scrim.
Don't
Don't build a scrim into the card — this component is the card only.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | Content, TransactionDetails, Container, Reference and ReferenceNumber all renamed to the family convention this pass. |
| C2 | Variant & Property Naming | Ready | ActionOrientation closed its space and both slots picked up the ⤷ prefix. hasReferenceNo. lost its trailing period this pass — it would not have survived as a native identifier in either language, and the name now traces 1:1. Nothing outstanding on this axis. |
| C3 | Token Coverage | Ready | Every colour is bound and named. Six belong to this component — bg/color-bg, bg/color-bg-main, border/color-border-weak, text/color-text, text/color-text-weaker and border/color-border-primary. The other four are the appearance/* set, which is the Button’s own variable mode arriving through ⤷ ActionSlot rather than anything this modal sets. All five text layers resolve to shared library styles. |
| C4 | Native Mappability | Ready | A column of text, a repeated row, a strip and an action area. Both slots map to view builders and the transaction rows are Modal - Transaction Receipt Entry instances, so the composition maps as cleanly as the primitive does. The one gap is that the three strings are text layers rather than properties — see C7. |
| C5 | Interaction State Coverage | Needs Refinement | Button states come from Button_New, and the transaction rows are display-only. What is missing is the copy control’s confirmation state — nothing in the component says a tap succeeded. The reference strip’s 40px height also falls short of the 44/48 tap minimum; the component ships live, so that is raised as a Design Recommendation rather than scored here. |
| C6 | Asset & Icon Quality | Ready | The copy glyph is a clean vector instance. Its master carries hidden icon-grid scaffolding, which is an icon-library concern rather than a fault here. |
1 component set × 2 ActionOrientation values = 2 variants. Everything above the action area is identical between them; row count comes from the slot, not from a variant.
| ActionOrientation | Size | Action area | Buttons | Node |
|---|---|---|---|---|
| Vertical | 320 × 434 | 320 × 156 | 2 × 272 × 50, primary on top | 5879:41046 |
| Horizontal | 320 × 376 | 320 × 98 | 2 × 132 × 50, primary on right | 5945:180029 |
State recommendation asked for the confirmation to be settled while the scorecard called it missing. One finding, one place — it is an Open Issue, and the half worth keeping from the recommendation, that which feedback to use is a product call, folded into it.hasReferenceNo. lost its trailing period. The name would not have survived as a native identifier in either language, so the mapping would have had to rename it and break the 1:1 trace Code Connect needs. It is hasReferenceNo in Figma and in both platform signatures now, spelled the same in all three.ActionOrientation is the driving property, so Horizontal and Vertical each get a card, in the panel’s order. The single card was titled with the component’s own name and specified Vertical only.hasDescription and hasReferenceNo, both defaulting to True; neither was on the page. Nothing changed in Figma for these two — get_node_info cannot read property definitions, and this is the fifth component in this run of reviews where the panel held more than the tree could show.Card inset, Row pitch, Button gap and Button radius. The seven canonical keys now come off the container’s own auto-layout panel: 434 or 376 tall and hugging, 320 wide, radius 6, no padding, no gap, top left.13px 24px — symmetric, and not what the panel says. It is 24 at the sides, 16 on top and none at the bottom, with a gap of 0, which is why the strip had been floating away from the action area. Its 40px height is the Copy icon’s 24 plus that 16.#title is Primary/Headlines/Section, #description is Primary/Multi-line Label/Light/Base, the reference row is Primary/Label/Light/Small, and the action label is Primary/Label/Large.bg/color-bg, bg/color-bg-main, border/color-border-weak, text/color-text, text/color-text-weaker, border/color-border-primary. The other four are the appearance/* set, which is the Button’s own variable mode arriving through ⤷ ActionSlot. The transaction rows are the same story: they are Entry instances, so their colours are that component’s..eb-preview-mtr declared font-family: inherit, which resolves to the site’s --font-body — BarkAda. Every text layer here carries a Primary/* style and is Proxima Soft in Figma, so the whole card was rendering in the wrong face. The root now names Proxima Soft; no per-layer override is needed, because nothing in this component is Secondary.gcash/east-blue-ios and com.gcash.eastblue:components:1.0.0, with no Import line. It now cites the Modal family artifact com.eastblue.ds:modal:1.0.0 and imports com.eastblue.ds.modal.*.#title, #description and the reference value came out — all three are text layers. They are named in the description instead, since they still have to be parameters natively..ebActionOrientation(...) — a SwiftUI modifier — against its own Compose column’s parameter, while the Style tab used a parameter in both. Settled on the parameter: actionOrientation changes the layout of the action area, not the component’s appearance. The same disagreement was found and settled the same way on the Entry primitive.title, description and the reference value are text layers rather than component properties, Code Connect will have no anchor for them when the native library lands. The five properties that do exist all trace 1:1. Recorded against C7, which was already open on registration.getSnippet, so both language tabs were frozen on a static string; they now track both booleans.18507:71705), which was carrying both a general-purpose dialog and this receipt layout in one set.Action Orientation renamed to ActionOrientation — the only variant property in the system that still had a space in it.content, transaction-details and container renamed to PascalCase; reference-offset renamed to Reference.For Receipt renamed to ReferenceNumber, with its text layers renamed to #label and #value. The space was closed in a follow-up so the layer matches TransactionDetails; the displayed copy still reads "Reference Number".Transaction Slot and Action Slot renamed to ⤷ TransactionSlot and ⤷ ActionSlot.Button - Large/Medium to Button_New, so both modals in the family share one button component.Transaction Receipt Modal to Modal - Transaction Receipt.