One row in a list — a leading marker, a filling label, and an optional trailing slot. Three nesting levels set the indent.
level became ListLevel, and the trailing slot that was only a suggestion now exists as ⤷ TrailingSlot behind a hasTrailing boolean. The third asked for the leading asset to become a Figma Slot; the rebuild kept it as a List Item - Asset instance instead, which reaches the same place by a better route — that component now carries its own Type=Slot, so arbitrary 16 × 16 content already has a way in without loosening this row to accept anything. This pass set #label to fill and closed the space in TrailingContainer. Nothing is outstanding on the component itself; Code Connect stays open because the native library does not exist yet.Shown as three rows so the indent reads against the left edge — a single row tells you nothing about what ListLevel does. Lists appear on terms pages, onboarding steps and task checklists.
ListLevel is PascalCase, hasTrailing follows the boolean prefix used by hasIcon and hasCTA, #label carries the # prefix and ⤷ TrailingSlot the arrow. TrailingContainer closed its space this pass.#label fills — so the row stretches to whatever width the list gives it rather than sitting at a fixed size.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| ListLevel=1 | N/A | N/A | indent 0 | Top-level row. The marker sits flush with the list's left edge. |
| ListLevel=2 | N/A | N/A | indent 16 | One level in. The label narrows by 16 because it fills the remaining width. |
| ListLevel=3 | N/A | N/A | indent 32 | Two levels in. Depth is carried by indentation alone — the marker and type stay the same at every level. |
| hasTrailing=false | N/A | N/A | default | TrailingContainer is hidden and the label runs to the right edge. |
| hasTrailing=true | N/A | N/A | 16 × 20 | Reveals ⤷ TrailingSlot at the end of the row. The slot ships empty. |
| Leading marker | N/A | N/A | instance | A List Item - Asset instance, Bullet by default. Swapping its Type is a nested instance property, not a property of this row. |
- The trailing slot exists now. The April assessment raised it as "consider a Trailing slot" — it is now
⤷ TrailingSlotinsideTrailingContainer, revealed by ahasTrailingboolean that defaults to false. C4 · Native Mappability - The level property was renamed.
levelbecameListLevel, matching the PascalCase convention used for variant properties across the system. C2 · Variant & Property Naming - The label fills instead of sitting at a fixed width. Every version is now 294 wide with
#labelat 277, 261 and 245 — the label absorbs the indent exactly, which is what fill looks like from the outside. A fixed label would have left the row unable to stretch to the width a real list gives it. C4 · Native Mappability - The leading asset stayed an instance, deliberately. The April assessment asked for it to become a Figma Slot so consumers could drop in any 16 × 16 component. It is still a List Item - Asset instance — and that is the better answer now, because the primitive carries its own
Type=Slot. Arbitrary content gets in through there, while this row keeps the constraint that its marker is a real design system marker. C4 · Native Mappability - TrailingContainer closed its space. It was
Trailing Container. Renamed this pass to matchTransactionDetailsand the other joined multi-word layer names in the system. C1 · Layer Structure & Naming - Depth is carried by indentation alone. All three levels use the same marker and the same type — BarkAda SemiBold 14 / 20 in
#445C85. Nested lists often alternate markers so depth reads without counting indents, andSquarewas available for it. Confirmed as intentional: the indent is the signal. C1 · Layer Structure & Naming - The sample copy means indentation, not type. The placeholder strings read "List body with level-1 style" through to level-3, which can be read as promising three different text styles when the type is identical at every level. Confirmed that "style" refers to the visual treatment of the indentation. C1 · Layer Structure & Naming
- The leading column is not pinned, which is what makes mixed markers break.
#labelstarts 8px after the marker ends, and the marker's width is set by whichever List Item - Asset version is in the instance — 9 for Bullet and Square, 12 for Numbered, 16 for the status icons. Swapping Bullet for Check therefore pushes the label 7px right, so a list mixing markers loses its left alignment. Mixing is out of scope by design and users are briefed, so this is a note rather than a fault — but pinning the marker to a fixed 16 and centring inside it would remove the constraint altogether and make the briefing unnecessary. Composition - Confirm what a wrapping label does to the marker. The row is 20 tall with a single-line label. Real list copy wraps, and when it does the marker should stay pinned to the first line rather than centring against the whole block. Worth setting the cross-axis alignment explicitly so it does not depend on the default. Composition
- Expose the list as a list to assistive tech. Rows should be children of a list container with their nesting level exposed, so a screen reader announces position and depth rather than reading a flat run of sentences. Indentation is a visual signal only — it does not reach assistive tech on either platform. A11y
- Publish the token names once Dev Mode is read.
#labelresolves to a library variable — verified on the component's own node — but the read-only tools return variable IDs rather than names, so the spec tables carry the hex value only. Token - See siblings: List Item - Asset supplies the leading marker and is the only other component in the family. List was removed in the 2026 rebuild — it was a frame of eight hardcoded rows rather than a container, so rows are now stacked with auto layout and list-level concerns like row spacing sit with whatever composes them. Family
The row paints one thing — its label. The marker's colour belongs to whichever List Item - Asset version is placed, which is why it is named here rather than given a value, and the surface is whatever the list sits on. Depth is carried by the indent alone: all three levels are identical in colour and in type.
| Role | Token | Value |
|---|---|---|
| #label | text/color-text-weak | #445C85 |
| Marker | set by the List Item - Asset version placed | — |
| Surface | none — inherits the list background | — |
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:list:1.0.0")
Import
import EastBlueDS import com.eastblue.ds.list.*
Planned API — the native library does not exist yet. The artifact is the List family: List, List Item and List Item - Asset all ship in <code>com.eastblue.ds:list</code> and import <code>com.eastblue.ds.list.*</code>.
Four properties: one enum-shaped variant, two booleans and a slot. Two native parameters have no row because Figma has no property behind them — label is the #label text layer, and marker is the nested List Item - Asset instance, which Figma lists under Nested instances rather than Properties. level is an Int, not a three-value enum. The Figma variant stops at 3 because a variant has to enumerate its values; a real list nests as deep as its content does, and capping the native API at three would bake a tooling limit into the platform. Same shape as hasWeek6 → weeks: Int on Date Picker - Calendar.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| ListLevel — 1, 2, 3 | level: Int = 1 | level: Int = 1 |
| hasLeading — true, false | hasLeading: Bool = true | hasLeading: Boolean = true |
| hasTrailing — true, false | hasTrailing: Bool = false | hasTrailing: Boolean = false |
| ⤷ TrailingSlot (slot) | @ViewBuilder trailing: () -> Trailing — shown when hasTrailing | trailing: @Composable (() -> Unit)? — shown when hasTrailing |
EBListItem("Delivery fee", level: 1)
EBListItem(label = "Delivery fee", level = 1)
EBListItem("Service charge", level: 2, marker: .square)
EBListItem(label = "Service charge", level = 2, marker = EBListItemAssetType.Square)
EBListItem("Processing", level: 3, marker: .checkPositive) { Image(systemName: "chevron.right") }
EBListItem(label = "Processing", level = 3, marker = EBListItemAssetType.CheckPositive) { Icon(EBIcons.ChevronRight) }
| Requirement | iOS | Android |
|---|---|---|
| Rows are exposed as a list | Wrap in a container with .accessibilityElement(children: .contain) and list traits | Modifier.semantics { collectionInfo = CollectionInfo(rowCount, 1) } |
| Nesting level is announced | Expose ListLevel in the row's accessibility value — indentation alone is invisible | CollectionItemInfo carries the row's depth |
| Decorative markers are hidden | Bullet, Square and Numbered are hidden; status markers put their meaning in the row's label | Same split — see List Item - Asset |
| Label wrapping is not truncated | No line limit on #label; the marker stays on the first line | maxLines = Int.MAX_VALUE, marker aligned to the top |
Do
Let the row fill the width its list gives it.
Don't
Don't set a fixed width on the row — the label is built to absorb the space.
Do
Use ListLevel for depth and keep the marker the same throughout.
Don't
Don't mix markers within one list — widths differ from 9 to 16 and the labels stop aligning.
Do
Turn on hasTrailing only when there is something to put in the slot.
Don't
Don't leave an empty trailing slot switched on; it reserves 16px for nothing.
Do
Keep the gap between rows on the auto-layout frame that stacks them.
Don't
Don't add vertical padding inside the row; it will double up with the list's spacing.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | TrailingContainer closed its space this pass. #label and ⤷ TrailingSlot carry the right prefixes, and identical type across levels is confirmed intentional. |
| C2 | Variant & Property Naming | Ready | ListLevel is PascalCase, and hasLeading and hasTrailing both follow the boolean prefix convention while staying off the variant name, so the set holds at three. The one caveat is that ListLevel has to enumerate at all — see C4. |
| C3 | Token Coverage | Ready | #label resolves to a library variable and carries a shared library text style — verified on the component's own node. Marker colour comes from the primitive. |
| C4 | Native Mappability | Ready | Maps to a single row view or composable. level is an Int natively rather than the three-value enum Figma shows: a variant has to enumerate, but a list nests as deep as its content does, and capping the API at three would bake a tooling limit into the platform. One constraint inherited from the marker: List Item - Asset is 9, 12 or 16 wide by version, so swapping the marker moves where the label starts. That is raised as a Design Recommendation on that component, not scored here. |
| C5 | Interaction State Coverage | Not Applicable | Display only — the row is not tappable. Anything interactive belongs to whatever is dropped into the trailing slot. |
| C6 | Asset & Icon Quality | Ready | No artwork of its own — the marker is a List Item - Asset instance and the trailing slot is whatever the consumer supplies. |
1 component set × 3 ListLevel values = 3 variants. hasTrailing is a boolean component property, so it toggles the trailing slot without doubling the set, and the marker is a nested instance rather than an axis.
| ListLevel | Indent | #label width | Row | Node |
|---|---|---|---|---|
| 1 | 0 | 277 | 294 × 20 | 5800:39052 |
| 2 | 16 | 261 | 294 × 20 | 5800:39056 |
| 3 | 32 | 245 | 294 × 20 | 5800:39060 |
.eb-preview-litem declared font-family: inherit, which resolves to BarkAda. #label is Secondary/Bold/Base and genuinely is BarkAda, so it happened to look correct — but the marker inherited the same face, and the numbered marker is Primary/Label/Light/Small, which is Proxima Soft. The root now names Proxima Soft and #label names BarkAda for itself, so each layer states its own face rather than depending on what it inherits.ListLevel is the driving property, so each of its values gets a card. The single card was titled with the component’s own name and specified level 1 only.hasLeading was undocumented. The Figma panel carries it, defaulting to True; neither the node tree nor this page had it at all. Nothing changed in Figma — get_node_info cannot read property definitions, and this is the fourth component in a row where the panel held more than the tree could show.ListLevel changes, and an indent is only legible against what it is indented from. Each card now renders the ladder down to its own level — level 1 alone, level 2 under a level 1, level 3 under both — which is also how the nesting actually works.Indent, #label width, Marker to label gap, Marker width and TrailingContainer. All seven are now read off the auto-layout panel: 20 — Hug, 294 — Fill, padding 0/16/32 left by level, gap 8, top left.BarkAda SemiBold · 14 / 20 · 0, a Face row, a Per level row, and "shared library style · name pending Dev Mode read". #label resolves to Secondary/Bold/Base.#label is text/color-text-weak at #445C85. The marker’s colour belongs to whichever List Item - Asset version is placed and the surface to the list beneath, so the new Colors by Level table says so rather than inventing values for them.hasLeading, so the renderer was reading a control that did not exist and silently falling back to true; and its server-rendered markup did not match what the script produces, so it would have been replaced on load. Both the live preview and all three cards are now generated by the same renderer.Properties holds the two booleans; Nested instances holds List Item - Asset. Figma lists them separately because the marker belongs to that component rather than to this row, and reproducing the split says something true about ownership.gcash/east-blue-ios and com.gcash.eastblue:components:1.0.0, with no Import line. It now cites the List family artifact com.eastblue.ds:list:1.0.0 and imports com.eastblue.ds.list.*, matching List Item - Asset.hasLeading had no row; #label is a text layer and the List Item - Asset instance is a nested instance, which Figma lists under Nested instances rather than Properties. Four rows now, one per panel property, with label and marker named in the description instead.level is an Int, not a three-value enum. The Figma variant stops at 3 because a variant has to enumerate its values; a real list nests as deep as its content does, and capping the native API at three would bake a tooling limit into the platform. Recorded in the mapping description and in C4, alongside the hasWeek6 → weeks: Int precedent from Date Picker - Calendar.EBListItem("Top level", level: 1) and the Style tab EBListItem(level: 1, label: "List body"). The Style tab moved: an unlabelled leading string matches EBButton("Save Changes"), which is the house convention. Kotlin names the parameter, having no equivalent idiom.ListLevel value now, with marker and the trailing slot riding along on levels 2 and 3 rather than taking subheadings of their own.getSnippet, so both language tabs were frozen on a static string.⤷ TrailingSlot now sits in TrailingContainer behind a hasTrailing boolean defaulting to false.level renamed to ListLevel, matching the PascalCase convention for variant properties.Type=Slot, so arbitrary content gets in without loosening this row.Trailing Container renamed to TrailingContainer.18482:34429 (Sticker Sheets v2) to 5728:37276 (2026 Working File).level uses string values — Should be integer or dropped in favor of nesting-based indent. Open