A display-only transaction row — a header of column labels, or a content row with a full-width label above however many peso-amount cells you drop into its slot.
Table Cell component that Table Row uses; its content slot holds the same Table Amount Cell that Table Scheduling uses. Settings are Role (Header / Content) and State (Default / Disabled), matching Table Row exactly, and no. of columns is gone — column count is however many cells sit in the ⤷ ColumnSlot. The raster peso is a Peso Sign - Proxima vector instance inherited from the shared amount cell. Kept as its own component because it carries a full-width #label row above its amount cells, which a plain Table Row doesn't. Rows are display-only, and amounts are unsigned by approved design. A later pass closed the last gap in that composition: the header had been overriding Table Cell's text style rather than asking for a different version of it, so the primitive gained Role=Transaction and the override is gone. Every colour is bound, and they are the same seven Table Row uses. The peso glyph is still an unflattened boolean operation — delegated to the Iconography team, and the one thing holding this at Needs Refinement.Per-variant descriptions cite account limits — e.g. daily / monthly send caps showing used vs. remaining peso amounts in aligned columns. Other GCash surfaces like transaction history and receipts use the Generic Transaction Card vertical stack, not this tabular layout.
⤷ ColumnSlot takes any number of cells, and the shared Table Amount Cell carries its own ⤷ CurrencySlot — a non-peso currency is a swap, not a detach.Peso Sign - Proxima vector instance inherited from the shared amount cell. The raster image fill and its remote asset URL are gone.Role and State match Table Row exactly, slots use the family's ⤷ …Slot form, and container is named the same way across all four versions. The no. of columns string-with-period property is gone.Table Cell in the header, Table Amount Cell in the content row. Nothing is re-implemented locally.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Header | Yes | Yes | Role=Header | 360 × 66 on a #F6F9FD ground. ⤷ ColumnSlot holds Table Cell copies at Role=Transaction, each with a 24 × 24 ⤷ AssetSlot above a Proxima Soft Semibold 12 label. |
| Content | Yes | Yes | Role=Content | 360 × 68 on white. A full-width #label in #6780A9 sits above a row of Table Amount Cell instances, each pairing a label with a peso-prefixed #value. |
| Disabled | Yes | Yes | State=Disabled | Text dims to #C2CFE5 across labels and values alike. |
| Pressed | N/A | N/A | Not built | Not needed — rows are display-only and carry no tap target. |
- The header cell has its own version instead of an override. v2.2: the header's
Table Cellcopies were painting over the primitive's text style —Table Row's header drawsPrimary/Label/Small, this one drewPrimary/Multi-line Label/Light/Fine, from the same version of the same component.Table Cellnow publishes aRole=Transactionversion that carries that style itself, so the two consumers declare their difference rather than one of them quietly repainting it. C2 · Variant & Property Naming - The label belongs to the content row by design. v2.2: reviewed and settled.
hasLabelandLabelappear on the panel for both roles, but the Header version has no#labellayer for them to act on — a header's column names live in its cells. Recorded so it is not re-raised as a missing layer. C2 · Variant & Property Naming - Every colour is bound to a named system colour. v2.2: checked against Figma's selection-colours panel, which the earlier tooling could not read. Seven paints, seven names, and they are the same seven
Table Rowuses — which is what makes the two read as one family rather than two components that happen to look alike. C3 · Token Coverage - It composes the family's atoms instead of duplicating them. v2.0: rebuilt on node
5896:39727. The header's⤷ ColumnSlotholds the sameTable Cellcomponent Table Row uses; the content row holds the sameTable Amount CellTable Scheduling uses. What were three parallel re-implementations are now one family sharing parts. C1 · Layer Structure & Naming -
no. of columnsis gone. v2.0: column count comes from the number of cells in⤷ ColumnSlot. The period-in-name string enum inherited from the old Table is no longer there to fix. C2 · Variant & Property Naming - The row maps to native primitives. v2.0: frames and slots throughout — a
VStack/Columnwith a nested row of cells. No platform table primitive required. C4 · Native Mappability - Interaction states are intentionally minimal. v2.0: reviewed and settled. Rows are display-only, so pressed and selected are not needed.
State=Disabledis built and dims text to#C2CFE5. Amounts carry no+/−prefix and no positive/negative colouring — that is the approved design, not a gap. C5 · Interaction State Coverage - The peso sign is a vector. v2.0: the raster image fill and its remote
figma.com/api/mcp/asset/*URL are gone, replaced by aPeso Sign - Proximainstance in⤷ CurrencySlot. The header's hardcoded#C2C6CFcircle is now a⤷ AssetSlotholding a swappablePlaceholder. C6 · Asset & Icon Quality - Staying a separate component is intentional. v2.0: reviewed and dismissed. Transaction keeps its own record because its content row carries a full-width
#labelabove the amount cells, which a plain Table Row entry doesn't. Same reasoning as Table Scheduling — and since both now build from shared atoms, the family reads as one. C1 · Layer Structure & Naming -
containeris named consistently. v2.1: all four versions use lowercasecontainer. The Header versions previously usedContainer. C1 · Layer Structure & Naming - The shared amount cell has a family-neutral name. v2.1:
Table Scheduling Amount Cellrenamed toTable Amount Cell. It is used by both Scheduling and Transaction, so the old name claimed a family it no longer belonged to exclusively. The rename propagated to both components. C1 · Layer Structure & Naming
- Document amount-sign semantics. Amounts here are unsigned by approved design — used, remaining, and cap totals rather than signed movements. Write that down so the component isn't reached for on flows that need
+/−; those belong to Generic Transaction Card. Docs - Flatten the peso glyph. For the Iconography team:
Peso Sign - Proximawraps ashape_fullBOOLEAN_OPERATION. Flattening it to a plain vector removes a class of export and scaling surprises. Shared with Table Scheduling. Asset - See siblings: Table Row and Table Scheduling — all three share
Table CellandTable Amount Cell. Keep slot naming and state coverage aligned across the family. Family
- Align the amount label token with Inline Text. v2.2: Applied — the alignment happened by both sides moving to the same place. This row draws from the system-wide
text/color-text-weakerandtext/color-text, and theinline-text/color/*set the recommendation wanted to align with no longer exists in the file. There is nothing left to reconcile here. What remains is on Inline Text's own page, which still documents those retired tokens. Token - Audit the colour token bindings. v2.2: Applied — done from Figma's selection-colours panel. All five colours this named are bound (
#F6F9FD,#E5EBF4,#0A2757,#6780A9,#C2CFE5), and so are the white background and the#D7E0EFasset placeholder it did not know about. Details in the Resolved tab. Token - Remove Table - Transaction from core DS, or fold it into Table as a recipe. v2.0: Settled — neither. It keeps its own record because its content row carries a full-width label above the amount cells, and it now composes the family's shared atoms rather than duplicating them. Family
- Consolidate with Table's row primitive. v2.0: Applied in substance — the header slot holds Table Row's
Table Celland the content slot holdsTable Amount Cell, so the parts are shared even though the record stays separate. Composition - Rename or drop
no. of columns. v2.0: Applied — dropped. Column count comes from the number of cells in⤷ ColumnSlot. Rename - Replace the raster peso sign with a text glyph or vector icon. v2.0: Applied — a
Peso Sign - Proximavector instance in⤷ CurrencySlot, inherited from the shared amount cell. Asset - Give the container one name across all versions. v2.1: Applied — lowercase
containeron all four. Rename - Give the shared amount cell a family-neutral name. v2.1: Applied —
Table Scheduling Amount Cellis nowTable Amount Cell, propagated to both Scheduling and Transaction. Rename
Seven paints, and the seven names Figma's selection-colours panel lists for node 5896:39727 — the same set Table Row uses, which is what makes the two read as one family. The ⤷ ColumnSlot holds a different primitive per role: Table Cell at Role=Transaction in Header, Table Amount Cell in Content with its own label switched off, so the row's single #label covers all three columns.
| Role | Element | Token | Value |
|---|---|---|---|
| Header | Background | bg/color-bg | #F6F9FD |
| — | Border | border/color-border-weak | #E5EBF4 |
| — | Cell text | text/color-text | #0A2757 |
| — | Cell asset | border/color-border | #D7E0EF |
| Content | Background | bg/color-bg-main | #FFFFFF |
| — | Border | border/color-border-weak | #E5EBF4 |
| — | #label | text/color-text-weaker | #6780A9 |
| — | Cell amount · currency | text/color-text | #0A2757 |
| Disabled | Every text layer | text/color-text-disabled | #C2CFE5 |
| — | Background · border · asset | — unchanged | – |
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:table:1.0.0")
Import
import EastBlueDS import com.eastblue.ds.table.*
Table Transaction publishes six properties. The rows beneath them map the two nested primitives — Table Cell and Table Amount Cell — neither of which has a page of its own; their Role and State are driven by the row, so neither takes a parameter here. One property behaves differently by role: ⤷ ColumnSlot holds Table Cell at Role=Transaction in a Header row and Table Amount Cell in a Content row, so it maps to two different arrays rather than one. And hasLabel with Label apply to Content only — the Header variant has no #label layer for them to act on, which is deliberate.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| Role — Header, Content | role: .header / .content | role = EBTableRowRole.Header / Content |
| State — Default, Disabled | .disabled(true) | enabled = false |
| hasLabel — true, false (Content only) | label == nil — nil hides the line | label == null |
| hasBorder — true, false | showsDivider: Bool = true | showsDivider: Boolean = true |
| Label (text, Content only) | label: String? | label: String? = null |
| ⤷ ColumnSlot (slot) — Table Cell in Header, Table Amount Cell in Content | columns: [Column] /amounts: [AmountCell] | columns: List<Column> /amounts: List<AmountCell> |
| Table Cell → Text (text) | Column.text: String | Column.text: String |
| Table Cell → hasAsset — true, false | Column.asset == nil — nil hides the slot | Column.asset == null |
| Table Cell → ⤷ AssetSlot (slot) | Column.asset: AnyView? | Column.asset: @Composable (() -> Unit)? |
| Table Amount Cell → Amount (text) | AmountCell.amount: String | AmountCell.amount: String |
| Table Amount Cell → Label (text) | AmountCell.label: String? | AmountCell.label: String? = null |
| Table Amount Cell → hasLabel — true, false | AmountCell.label == nil — false here; the row's own Label covers all columns | AmountCell.label == null |
| Table Amount Cell → ⤷ CurrencySlot (slot) | AmountCell.currency: AnyView? | AmountCell.currency: @Composable (() -> Unit)? |
// One Header row above the content rows it labels. EBTableTransactionRow( role: .header, columns: [ Column("Daily", asset: Image("calendar")), Column("Weekly", asset: Image("week")), Column("Monthly", asset: Image("month")) ] )
// One Header row above the content rows it labels. EBTableTransactionRow( role = EBTableRowRole.Header, columns = listOf( Column("Daily"), Column("Weekly"), Column("Monthly") ) )
// The row's Label names all three columns at once. EBTableTransactionRow( role: .content, label: "Transaction limit", amounts: [ AmountCell("3,500.00"), AmountCell("20,000.00"), AmountCell("80,000.00") ] )
// The row's Label names all three columns at once. EBTableTransactionRow( role = EBTableRowRole.Content, label = "Transaction limit", amounts = listOf( AmountCell("3,500.00"), AmountCell("20,000.00"), AmountCell("80,000.00") ) )
| Requirement | iOS | Android |
|---|---|---|
| Amount semantics | Each amount cell should read as a single element — wrap peso + value and use .accessibilityLabel("Three thousand five hundred pesos"). | Merge peso + value with Modifier.semantics(mergeDescendants = true); set contentDescription to a spoken phrase, not the raw glyph. |
| Currency glyph fallback | Use Unicode \u{20B1} inline — avoid a raster image that won't scale with Dynamic Type. | Use Unicode ₱ inline — avoid a bitmap that won't respect font-scale settings. |
| Header vs content row | Header rows carry .accessibilityAddTraits(.isHeader). | Header rows use Modifier.semantics { heading() }. |
| Column label / amount pairing | Group the preamble label with each amount column so VoiceOver reads "Daily, three thousand five hundred pesos". | Group the preamble label with each amount column so TalkBack reads "Daily, three thousand five hundred pesos". |
Do
Use one Header row at the top, naming the columns beneath it.
Don't
Don't repeat it between groups — the header's cells carry column labels, not group titles.
Do
Put the row's subject in Label and only figures in the cells.
Don't
Don't reach for hasLabel on a Header row — that variant has no #label layer, so nothing happens.
Do
Give every row in one table the same number of cells.
Don't
Don't mix 3 in the header and 4 below: the ⤷ ColumnSlot divides its 312px evenly, so the two rows land on 104px and 78px columns and nothing lines up.
Do
Let the platform format each amount and its currency symbol.
Don't
Don't ship ₱ as literal text beside a preformatted number — the glyph is a slot so it can follow the locale.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | One container per role, a single ⤷ ColumnSlot, and text layers on the #… form. The primitives it composes carry their own names rather than being redrawn here. |
| C2 | Variant & Property Naming | Ready | Role and State are PascalCase with Title Case values, matching Table Row; two has* booleans and one text property named for what it holds. hasLabel and Label apply to Content only — deliberate, since the Header role has no label line. |
| C3 | Token Coverage | Ready | Verified against the selection-colours panel: seven paints, seven names — bg/color-bg, bg/color-bg-main, border/color-border, border/color-border-weak, text/color-text, text/color-text-weaker and text/color-text-disabled. The same set Table Row uses, which is what makes the two read as one family. |
| C4 | Native Mappability | Ready | A row of evenly divided cells on both platforms — no table primitive needed. The one wrinkle is that ⤷ ColumnSlot holds a different primitive per role, so it maps to two arrays rather than one. |
| C5 | Interaction State Coverage | Ready | Display-only by design, so pressed and selected have nothing to show. State=Disabled dims every text layer to text/color-text-disabled and leaves background, border and asset alone. Both primitives publish their own Disabled version, so the row swaps to them rather than overriding fills per copy. |
| C6 | Asset & Icon Quality | Needs Refinement | The Peso Sign - Proxima instance inside each Content cell draws an unflattened BOOLEAN_OPERATION rather than a flat vector. It renders correctly and is tracked as a design recommendation; flattening is the iconography team's to do. |
Role (2) × State (2) = 4 published versions. Two booleans and a text property sit on top without adding any — hasBorder on every row, hasLabel and Label on Content only — so the row has 8 combinations behind 4 versions. Cell count is not a version either: the ⤷ ColumnSlot takes however many cells you drop in, three in every published variant, and which primitive fills it follows the role — Table Cell at Role=Transaction in Header, Table Amount Cell in Content.
| Role | State | Dimensions | Background | Node |
|---|---|---|---|---|
| Header | Default | 360 × 66 | #F6F9FD | 5896:39740 |
| Header | Disabled | 360 × 66 | #F6F9FD | 5900:40254 |
| Content | Default | 360 × 68 | #FFFFFF | 5896:39762 |
| Content | Disabled | 360 × 68 | #FFFFFF | 5900:40274 |
Table Cell copies were painting over the primitive's text style — Table Row's header draws Primary/Label/Small, this one drew Primary/Multi-line Label/Light/Fine, from the same version of the same component. Table Cell now publishes Role=Transaction (nodes 9567:96672 and 9567:96676) carrying that style itself. The defect was invisible from either side: both components looked correct alone, and only comparing the two consumers showed one repainting the other.hasLabel and Label appear on the panel for both roles, but the Header version has no #label layer for them to act on — a header's column names live in its cells. Reviewed and recorded so it is not re-raised as a missing layer.Table Row uses. This closes the standing recommendation to audit the bindings, which had listed five colours and missed the #D7E0EF asset placeholder.text/* set, and the inline-text/color/* tokens no longer exist in the file. Nothing left to reconcile here — what remains is on Inline Text's own page, which still documents the retired tokens.47:324703 and 47:324708 from before the rebuild. Heights read 36 and 72.5 against a real Hug · 66 and Hug · 68, and both text style names were wrong — Primary/Multi-line Label/Light/Tiny for a style that is /Fine, and Primary/Label/Small for one that is Primary/Label/Light/Fine.container, 24 both sides with 12 top and bottom on Header and 16 on Content. The two containers also carry different gaps — 10 on Header, 8 on Content — both inert today since Header has a single child. Documented as Figma has them rather than normalised, since removing either could disturb something already placed.Table family's coordinates. Property Mapping went from 10 rows to 13, regrouped into prose, with Label added (it had no row at all) and the two nested primitives mapped properly. Usage Snippets split one per Role; four usage guidelines written where there were none.Table Row maps Table Cell to Column, and Table Scheduling maps Table Amount Cell to Column as well. This row is where both appear together, so it would have been the first place they collided in one API. Table Amount Cell is AmountCell here; Table Scheduling's five rows still need the same rename.7f5cafa (August 2026) and never got changelog entries, while the Overview tab referenced both throughout. Both are written above, reconstructed from that commit and from the Overview's own version-tagged records.Table Scheduling and this row both now do.Table Row already drew. Its ⤷ ColumnSlot now holds the same Table Cell component, and the content row holds the same Table Amount Cell that Table Scheduling uses.no. of columns fixed the number of cells at build time; the slot now takes however many you drop in.Peso Sign - Proxima instance inherited from the shared amount cell.Table Row as a recipe. It keeps a full-width label above a row of amount cells, which a standard row has no shape for, so it stays separate and shares the atoms instead.no. of columns inherited naming — String enum with a period in the property name, carried over from Table. Open#C2C6CF. Open