KeepNeeds Refinement
Table Row Component link

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.

Keep — rebuilt as one composable row
The old three-component setup (Table + Table - Item + Table - Label) is gone, and with it the 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.
In Context

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.

Title Header Column Column Column Label Desc Desc Desc Label Desc Desc Desc Label Desc Desc Desc Label Desc Desc Desc Label
Live Preview
Header
Title
Title
Title
Properties
Role
State
hasBorder
⤷ Table Label
Title
hasAsset
hasDescription
⤷ ColumnSlot
Table Cell ⤷ AssetSlot
Table Cells
DS Health
Reusable
Pass
The 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.
Self-contained
Pass
Row carries its own background, border, and typography per role. Nothing is locked at build time any more — the old fixed column matrix is gone.
Consistent
Pass
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.
Composable
Pass
Real composition at last — 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.
Behavior
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.
Issues
  • 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:37611 as Table Row. Table Label and Table Cell are 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 columns is gone. v2.0: column count is no longer a property. The Columns Slot holds however many Table Cell instances 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 Cell carries a 24 × 24 Asset Slot holding a swappable Placeholder instance, and Table Label has one too. The hardcoded #C2C6CF circle and the icon=yes/no boolean are both gone. C6 · Asset & Icon Quality
  • The row maps to native primitives. v2.0: a slot-based row is just an HStack / Row of cells, so it no longer depends on a platform Table primitive 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: #description moved 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: #description now takes #C2CFE5 in State=Disabled (5761:37774), matching #label and 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 ⤷ …Slot form — ⤷ ColumnSlot on the row, and ⤷ AssetSlot on both Table Label and Table 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=Disabled is built for rows showing unavailable data. C5 · Interaction State Coverage
Recommendations
  • Move #description from 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 #C2CFE5 disabled 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/label with main/inline-text/* reduces drift and helps cross-component theming. Token
  • Collapse the three-component family into one row primitive. v2.0: Applied — Table Row with a Columns Slot and a Role variant, exactly as proposed. 3 components and 14 variants become 1 component and 4. Property
  • Rename no. of columns to an integer, or drop it entirely. v2.0: Applied — dropped. Column count comes from the number of Table Cell instances in the slot. Rename
  • Replace the header icon placeholder with a named slot. v2.0: Applied — Asset Slot on both Table Label and Table Cell, and the icon=yes/no boolean is gone. Slot
  • Add row interaction states. v2.0: Applied as far as it goes — State=Disabled shipped. 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 — #description is now BarkAda SemiBold 10 / 15, on the shared 10px token. The move to 12px stays open as a recommendation. A11y
  • Dim #description in the disabled state. v2.1: Applied — it takes #C2CFE5 alongside the label and cells. Token
  • Settle one slot naming convention for the component. v2.1: Applied — ⤷ ColumnSlot and ⤷ AssetSlot throughout. Settling it library-wide stays open as a Docs item. Rename
Types
Header row
DES DEV

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.

Properties
State
Table Cells
⤷ Asset Slot
Properties
Columns 4
Icon no
Colors
Surface bg #F6F9FD
Border #E5EBF4
Label #0A2757
Column #0A2757
Icon placeholder #C2C6CF
Layout
Height 37
Padding H 24
Padding V 8 / 12
Icon size 24 × 24
Typography
Header style Primary/Label/Light/Base
Header font Proxima Soft Semibold · 16 / 16 · +0.25
Body style Primary/Label/Small
Body font Proxima Soft Bold · 14 / 14 · +0.25
Colors by State
Role Token DefaultPressedDisabled
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
Content row
DES DEV

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>.

Properties
State
Table Cells
⤷ Asset Slot
Properties
Columns 4
Colors
Surface bg #FFFFFF
Border #E5EBF4
Label #0A2757
Description #6780A9
Layout
Height 56
Padding H 24
Padding V 12
Typography
Header style Primary/Label/Light/Base
Header font Proxima Soft Semibold · 16 / 16 · +0.25
Body style Primary/Label/Small
Body font Proxima Soft Bold · 12 / 12 · +0.5
Layout
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
Installation Planned API

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")
}
Property Mapping
Figma PropertySwiftUICompose
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)?
SwiftUI
ios/Components/Table/EBTableRow.swift
Jetpack Compose
android/components/table/EBTableRow.kt
Usage Snippets Planned API
Usage
// 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")
}
Accessibility
RequirementiOSAndroid
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.
Criteria Scorecard
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.
Variants Inventory (4 total)

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.

RoleStateDimensionsBackgroundNode
HeaderDefault360 × 68#F6F9FD5734:37630
HeaderDisabled360 × 68#F6F9FD5761:37748
ContentDefault360 × 70transparent5734:37657
ContentDisabled360 × 70#F6F9FD5761:37773
1.0.0 — April 2026Major
Initial Assessment · node 47:326260
Family assessed — 3 published components (Table / Table - Item / Table - Label) with 9 + 3 + 2 variants. Only Table is consumed in screens. Documented
Initial
Three orphan components — Table - Item and Table - Label exist but are never placed directly. Recommend collapsing into one data-driven row. Open
C1 Open
no. of columns naming — String enum with a period in the property name. Open
C2 Open
No native mobile primitive — Mobile tables are almost always stacks of label/value pairs (Inline Text) — reconsider whether Table belongs in the DS at all. Open
C4 Open
No interaction states — No hover / pressed / selected / disabled state variants. Open
C5 Open
Header icon is a raw placeholder — Hardcoded #C2C6CF circle with no slot or instance swap. Open
C6 Open
Code Connect mappings — Not registered. Open
C7 Open