A display-only table row — a leading label and however many data cells you drop into its Columns Slot. Comes in Header and Content roles, each with a disabled state.
no. of columns variant matrix. Table Row now composes Table Label and Table Cell as real instances through slots, so column count is whatever you put in the Columns Slot rather than a property to pick from. Settings are Role (Header / Content) and State (Default / Disabled) — 4 versions in place of 3 components and 14 variants. Rows are display-only by design, so there is no pressed or selected state. The follow-up pass cleared the rest: #description moved to the 10px token, the disabled state now dims every text layer, and all three slots settled on ⤷ …Slot. Code Connect stays unmapped because the native library doesn't exist yet.Sticker sheet shows Table instances stacked on a Template Screen to build a static 6-row pattern — 1 header row + 5 content rows. No scroll, no sort, no selection.
Columns Slot takes any number of Table Cell instances, and each cell carries its own Asset Slot and text. Column count, icons, and cell content are all the consumer's to set.Role and State are PascalCase variant properties with Title Case values, and State=Default | Disabled is a valid subset of the standard interaction set. The no. of columns property, with its period, is gone.Table Label and Table Cell are placed as instances inside slots rather than redrawn. The old complaint was that both atoms were published but never actually slotted.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Header | Yes | Yes | Role=Header | 360 × 68 on a #F6F9FD ground. Label is Proxima Soft Bold 14 / 14; each cell's text is Bold 14 too. |
| Content | Yes | Yes | Role=Content | 360 × 70, transparent ground. Label drops to Bold 12 / 12 and cell text becomes BarkAda Regular 12 / 18. |
| Disabled | Yes | Yes | State=Disabled | Every text layer — label, description, and all cells — dims to #C2CFE5. The row takes a #F6F9FD ground, the same colour a Header row uses. |
| Pressed | N/A | N/A | Not built | Not needed — rows are display-only and carry no tap target. |
| Selected | N/A | N/A | Not built | Not needed — no row selection in this pattern. |
- Code Connect mappings not registered. Blocked — the native component library doesn't exist yet. Nothing to action on the design side. C7 · Code Connect Linkability
- One row replaces three components. v2.0: rebuilt on node
5734:37611asTable Row.Table LabelandTable Cellare now placed as real instances inside slots instead of being published and ignored. 3 components and 14 variants become 1 component and 4. C1 · Layer Structure & Naming -
no. of columnsis gone. v2.0: column count is no longer a property. TheColumns Slotholds however manyTable Cellinstances you drop in, which is what data-driven was always meant to mean. The period in the property name goes with it. C2 · Variant & Property Naming - The icon placeholder is a slot. v2.0: every
Table Cellcarries a 24 × 24Asset Slotholding a swappablePlaceholderinstance, andTable Labelhas one too. The hardcoded#C2C6CFcircle and theicon=yes/noboolean are both gone. C6 · Asset & Icon Quality - The row maps to native primitives. v2.0: a slot-based row is just an
HStack/Rowof cells, so it no longer depends on a platformTableprimitive that phones don't have. Rows stay 360 wide for now because they sit inside a fixed component group; they move to fill when the screens that need it are reworked. C4 · Native Mappability - The label description is readable now. v2.1:
#descriptionmoved from 8 / 12 to the 10px token used elsewhere in the system — BarkAda SemiBold 10 / 15. 10px is the accepted middle ground; moving to the 12px token stays open as a recommendation. C3 · Token Coverage - The disabled state dims every text layer. v2.1:
#descriptionnow takes#C2CFE5inState=Disabled(5761:37774), matching#labeland the cells. A disabled row dims as one thing. C5 · Interaction State Coverage - Slot layer names follow one pattern. v2.1: all three slots renamed to the
⤷ …Slotform —⤷ ColumnSloton the row, and⤷ AssetSloton bothTable LabelandTable Cell. Table Scheduling uses the same pattern. C1 · Layer Structure & Naming - Interaction states are intentionally minimal. v2.0: reviewed and settled. Rows are display-only — no tap target, no selection — so pressed, focused, and selected are not needed.
State=Disabledis built for rows showing unavailable data. C5 · Interaction State Coverage
- Move
#descriptionfrom the 10px token to the 12px one. 10px was chosen as a middle ground and matches other components today, so this isn't urgent. 12px is the target — it's the floor the rest of the system's body text sits on, and small supporting text is exactly where legibility is thinnest. A11y - Give disabled rows their own background. Disabled currently uses
#F6F9FD, the same ground as a Header row, so the two read alike in a stack. Either give disabled a distinct tint or let it keep the row's own background and carry the state through text colour alone. Token - Write the slot naming convention into the guidelines. The Table family now uses
⤷ …Slot—⤷ ColumnSlot,⤷ AssetSlot,⤷ CurrencySlot. The carousel components use⤷without the suffix (⤷ LeadingIcon,⤷ Violator) and no prefix at all on top-level slots (Banner,Background). Both are internally consistent; the library needs one rule so new slot-based components stop diverging. Docs - Document that rows are display-only. There is no tap target and no selection by design. Write it down so native developers wrap the row in a non-interactive container rather than inferring a missing state. Docs
- Revisit the fixed 360 width when screens are reworked. Rows are 360 wide because they currently sit inside a fixed component group. The intent is to move to fill. Worth tracking so it doesn't get forgotten once those screens are edited. Composition
- Audit the colour token bindings. The review tooling reads raw hex and can't see variable bindings, so C3 is recorded as unverified rather than passing. Confirm
#F6F9FD,#E5EBF4,#0A2757,#6780A9, and the#C2CFE5disabled colour are all bound. Token - Introduce a shared label/value token set with Inline Text. A table label and an Inline Text label play the same role — a thing and its value. Aligning
main/table/color/labelwithmain/inline-text/*reduces drift and helps cross-component theming. Token
- Collapse the three-component family into one row primitive. v2.0: Applied —
Table Rowwith aColumns Slotand aRolevariant, exactly as proposed. 3 components and 14 variants become 1 component and 4. Property - Rename
no. of columnsto an integer, or drop it entirely. v2.0: Applied — dropped. Column count comes from the number ofTable Cellinstances in the slot. Rename - Replace the header icon placeholder with a named slot. v2.0: Applied —
Asset Sloton bothTable LabelandTable Cell, and theicon=yes/noboolean is gone. Slot - Add row interaction states. v2.0: Applied as far as it goes —
State=Disabledshipped. Pressed and selected were reviewed and dropped: rows are display-only. State - Reconsider whether Table belongs in a mobile-first DS. v2.0: Settled — Table stays, scoped to genuine multi-column data and rebuilt as a data-driven row. That was option (b) of the two paths originally offered. Family
- Raise the label description above 8px. v2.1: Applied —
#descriptionis now BarkAda SemiBold 10 / 15, on the shared 10px token. The move to 12px stays open as a recommendation. A11y - Dim
#descriptionin the disabled state. v2.1: Applied — it takes#C2CFE5alongside the label and cells. Token - Settle one slot naming convention for the component. v2.1: Applied —
⤷ ColumnSlotand⤷ AssetSlotthroughout. Settling it library-wide stays open as a Docs item. Rename
360 × 68 on a <code>#F6F9FD</code> ground with a bottom border. <code>Table Label</code> on the left at Proxima Soft Bold 14; each <code>Table Cell</code> stacks a 24 × 24 <code>⤷ Asset Slot</code> above Bold 14 text.
| Role | Token | Default | Pressed | Disabled |
|---|---|---|---|---|
| Header bg | main/table/color/bg-subtle | #F6F9FD | – | – |
| Content bg | main/table/color/bg | #FFFFFF | – | – |
| Row border | main/table/color/border | #E5EBF4 | – | – |
| Label / column text | main/table/color/label | #0A2757 | – | – |
| Description text | main/table/color/description | #6780A9 | – | – |
| Header icon placeholder | — (hardcoded) | #C2C6CF | – | – |
360 × 70 on a transparent ground. <code>Table Label</code> drops to Proxima Soft Bold 12, and each <code>Table Cell</code> renders BarkAda Regular 12 / 18 beneath its <code>⤷ Asset Slot</code>.
| Role | Token | Value |
|---|---|---|
| Row width (fixed) | — | 360px |
| Header height (icon=no) | — | 37px |
| Header height (icon=yes) | — | 65px |
| Content row height | — | 56px |
| Horizontal padding | space/space-24 | 24px |
| Header pt / pb | space/space-8, space/space-12 | 8 / 12px |
| Content py | — | 12px |
| Column gap | — | 16px |
| Label width | — | 99px min |
| Header icon size | — | 24 × 24px |
| Icon → column gap | space/space-2 | 2px |
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:table:1.0.0") }
| Figma Property | SwiftUI | Compose |
|---|---|---|
Role = Header | Content | role: .header / .content | role = EBTableRowRole.Header / Content |
State = Default | Disabled | .disabled(true) | enabled = false |
hasBorder: Boolean | showsDivider: Bool = true | showsDivider: Boolean = true |
⤷ ColumnSlot (N × Table Cell) | columns: [Column] | columns: List<Column> |
Table Label → Title (text) | title: String | title: String |
Table Label → hasDescription | description: String? — nil hides it | description: String? = null |
Table Label → hasAsset | leadingIcon: AnyView? — nil hides it | leadingIcon: @Composable (() -> Unit)? = null |
Table Label → ⤷ AssetSlot | leadingIcon: AnyView? | leadingIcon: @Composable (() -> Unit)? |
Table Cell → #description | Column.text: String | Column.text: String |
Table Cell → ⤷ AssetSlot | Column.asset: AnyView? | Column.asset: @Composable (() -> Unit)? |
// Option A — dedicated row primitive (if Table stays in DS) VStack(spacing: 0) { EBTableRow( role: .header, label: "Header", columns: ["Column", "Column", "Column"] ) ForEach(rows) { row in EBTableRow( role: .content, label: row.label, columns: row.values ) } } // Option B — compose with Inline Text (recommended for mobile) VStack(spacing: 12) { EBInlineText(label: "Amount", value: "₱1,000.00") EBInlineText(label: "Reference", value: "0000 0123 4567") EBInlineText(label: "Date", value: "Apr 22, 2026") }
// Option A — dedicated row primitive (if Table stays in DS) Column { EBTableRow( role = EBTableRowRole.Header, label = "Header", columns = listOf("Column", "Column", "Column") ) rows.forEach { row -> EBTableRow( role = EBTableRowRole.Content, label = row.label, columns = row.values ) } } // Option B — compose with Inline Text (recommended for mobile) Column(verticalArrangement = Arrangement.spacedBy(12.dp)) { EBInlineText(label = "Amount", value = "₱1,000.00") EBInlineText(label = "Reference", value = "0000 0123 4567") EBInlineText(label = "Date", value = "Apr 22, 2026") }
| Requirement | iOS | Android |
|---|---|---|
| Tabular semantics | On iPad/macOS use Table; on iPhone use accessibilityElement(children: .combine) per row so each reads as "Label, value, value". | Wrap in Modifier.semantics { collectionInfo = CollectionInfo(rowCount, columnCount) }. |
| Header vs content row | Use .accessibilityAddTraits(.isHeader) on header rows. | Use Modifier.semantics { heading() } on header rows. |
| Column headers without visible text | If icon-only header columns exist, provide .accessibilityLabel. | Set contentDescription on the icon slot. |
| Row-level tap | If rows become tappable, wrap row in Button with .accessibilityHint. | Wrap in Modifier.clickable with role = Role.Button. |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | One Table Row composing Table Label and Table Cell instances through slots, all named ⤷ …Slot. |
| C2 | Variant & Property Naming | Ready | Role and State are PascalCase with Title Case values. The no. of columns property and its period are gone. |
| C3 | Token Coverage | Needs Refinement | Not verified — the read-only tooling can't see variable bindings. #description now sits on the shared 10px token; moving it to 12px is an open recommendation. |
| C4 | Native Mappability | Ready | A slot-based row is an HStack / Row of cells — no platform Table primitive needed. Fixed 360 width is deliberate for now and moves to fill when the screens are reworked. |
| C5 | Interaction State Coverage | Ready | Display-only by design, so pressed and selected are not needed. State=Disabled dims every text layer to #C2CFE5. It still reuses the Header background — tracked as a recommendation. |
| C6 | Asset & Icon Quality | Ready | 24 × 24 Asset Slot on both Table Label and Table Cell, holding a swappable Placeholder instance. |
| C7 | Code Connect Linkability | Not Mapped | Blocked — the native component library doesn't exist yet. |
Role (2) × State (2) = 4 published versions, listed below. Three booleans sit on top of them without adding variants — hasBorder on the row, and hasAsset and hasDescription on the nested Table Label — so the real combination count is 32. Column count isn't a version either: the ⤷ ColumnSlot takes however many Table Cell instances you drop in. This replaces the old three-component family, which published 9 + 3 + 2 variants between them.
| Role | State | Dimensions | Background | Node |
|---|---|---|---|---|
| Header | Default | 360 × 68 | #F6F9FD | 5734:37630 |
| Header | Disabled | 360 × 68 | #F6F9FD | 5761:37748 |
| Content | Default | 360 × 70 | transparent | 5734:37657 |
| Content | Disabled | 360 × 70 | #F6F9FD | 5761:37773 |
no. of columns naming — String enum with a period in the property name. Open#C2C6CF circle with no slot or instance swap. Open