A tappable content card — slotted leading icon, a heading with tag and badge, two label-and-description rows, and a slotted trailing chevron.
5412:31504 in the 2026 Working File as Status = Default | Skeleton × State = Default | Disabled × IconSize = XL | LG | MD | SM | XS | XXS over 18 variants. The naming pass is complete: all three properties PascalCase per §1 with Title Case values per §5, the ⤷ glyph and the # sigil both gone, text layers on the §3 vocabulary and §7 hierarchy, frames PascalCase with the two meaningless offset frames replaced by PreambleContainer and IconContainer, and both slots kebab-cased with the -Slot suffix the rest of the system uses. Splitting Skeleton onto its own Status axis matches §6 and Upload File. The paired TextContainer rows are confirmed intentional peers, the state coverage is confirmed complete at 18 variants, and all six icon sizes are confirmed in use. All four DS Health traits pass; the only item still open is Code Connect, blocked until the native library exists.Generic Card stacks vertically into a scrolling list — product catalogs, service menus, transaction history detail screens. Icon size tightens as the density of the list increases.
Heading Goes Here
Label:Description goes here
Label:Description goes here
LabelBadge and Chevron Right instances rather than redrawing them.Status, State and IconSize are all PascalCase per §1 with Title Case values per §5 on the standard sizing vocabulary; Skeleton sits on Status rather than competing with interaction on State, per §6; text layers follow the §3 vocabulary and the §7 hierarchy; frames are PascalCase throughout, including the skeleton variants; and both slots are kebab-cased with the -Slot suffix used across the system.leadingIcon and trailingIcon are both real Figma Slots, so the artwork and the trailing affordance are consumer-supplied, and the badges are shared instances.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default | Yes | Yes | state=Default | Normal row — all content visible, chevron shown when hasChevron. |
| Skeleton (loading) | Yes | Yes | state=skeleton | Loading pattern — gray rounded placeholders where each content slot would render. Kudos for shipping this as a first-class variant. |
| Pressed | N/A | N/A | Not built | Tappable row (has chevron) — needs a pressed state with background tint for tap feedback. |
| Disabled | N/A | N/A | Not built | For temporarily-unavailable services (e.g. maintenance). Typically dimmed label + muted icon. |
- Leading icon is a real Figma Slot. v2.0: Rebuilt on node
5412:31504in the 2026 Working File.leadingIconis a genuineSLOTholding aPlaceholderinstance as its swap target, replacing the hardcoded circle the previous assessment flagged. A consumer supplies their own artwork without detaching, and native handoff has a real content slot to bind. (C1 · Slot) C1 · Layer Structure & Naming - Chevron is a vector instance in a slot. v2.0: The raster chevron is gone.
trailingIconis aSLOTcarrying aChevron Rightinstance from the shared icon library, so it recolours from tokens and stays crisp at any density — and because it is slotted, a card that needs a different trailing affordance can swap it. (C6 · Asset) C6 · Asset & Icon Quality -
iconSizevalues moved from numeric to semantic. v2.0: The six numeric values are replaced by a t-shirt scale —xl | l | m | s | xs | xxs— which is the naming half of the previous recommendation. The count is unchanged at six, and whether all six earn their place is tracked separately below. (C2 · Rename) C2 · Variant & Property Naming - Disabled state added. v2.0:
state=disabledships across all six icon sizes, closing half of the missing-states finding. Pressed is still absent and is tracked below. (C5) C5 · Interaction State Coverage -
skeletonmoved onto its ownStatusaxis. v2.1: The schema is nowStatus = Default | Skeleton×State = Default | Disabled×iconSize, matching the §6 rule and the shape Upload File uses. A skeleton is the system reporting that content has not arrived; disabled is how the user may interact with it. Separating them means the two no longer compete for one axis. (C2 · Property) C2 · Variant & Property Naming -
StateandStatusPascalCase, all values Title Case. v2.1:Default,Disabled,Skeletonper §5, and the icon scale is nowXL | LG | MD | SM | XS | XXS— aligned to the standard sizing vocabulary rather than the single-letterl | m | sit used before. (C2 · Rename) C2 · Variant & Property Naming - The
⤷glyph is gone and slots are kebab-cased. v2.1:⤷ icon,⤷ leadingIconand⤷ trailingIconare nowMediaContainer,Leading-IconandTrailing-Icon— the literal arrow character is removed from every layer, and both slots follow §4 kebab-case, so nothing in the tree blocks a generated identifier. (C1 · Rename) C1 · Layer Structure & Naming - Legacy sigil dropped and frames moved to PascalCase. v2.1:
#tag→Preamble,#name→Title,#label→Label,#description→Description, putting the text layers on the §3 vocabulary and the §7 hierarchy. The frames follow:container→ContentRow,content-block→ContentBlock,text-container→TextContainer,badge-container→BadgeContainer. Both frames namedoffsetare gone, replaced byPreambleContainerandIconContainer, which say what they hold. (C1 · Rename) C1 · Layer Structure & Naming - Skeleton variants brought onto the shared vocabulary. v2.1:
icon-container— with its trailing space — is nowMediaContainer,content-blockisContentBlock, and theoffsetframe standing in for the chevron isIconContainer, all matching the names their default siblings use. A reviewer no longer needs a second mental model to read a skeleton variant. (C1 · Rename) C1 · Layer Structure & Naming - Two
TextContainerrows confirmed intentional peers. v2.2: TheCoverageblock holds two identically-structured rows by design — each aLabelandDescriptionpair, the second carrying a further line item rather than a different kind of content. Sharing a name is correct here: they are peers, not a primary and a secondary, and naming them apart would imply a hierarchy the card does not have. Note for implementation: identically-named siblings are matched by order when Figma swaps variants, so aniconSizechange re-matches the two rows positionally. That is reliable while both rows are present and in order, and it is the reason to keep them as a fixed pair rather than making either one optional. Native implementations should model this as a list of two label-value rows rather than as two named properties. (C1 · Docs) C1 · Layer Structure & Naming - State coverage confirmed as-is. v2.2:
State = Default | Disabledis the intended coverage — no pressed state, andStatus=Skeletondeliberately ships only withState=Default. A skeleton is a placeholder for content that has not arrived, so it cannot meaningfully be disabled, and the 18 built variants are therefore the complete set rather than 18 of a nominal 24. Native implementations should treatSkeleton×Disabledas unreachable and should not expect a pressed treatment from the component. (C5) C5 · Interaction State Coverage -
iconSize→IconSize. v2.3: Verified on the live node — all 18 variants readStatus×State×IconSize. Every property in the set is now PascalCase per §1 with Title Case values per §5, closing the property pass. (C2 · Rename) C2 · Variant & Property Naming - Slot suffix aligned to the family convention. v2.3:
Leading-IconandTrailing-Iconare nowLeading-Icon-SlotandTrailing-Icon-Slot— kebab-case per §4 with the-Slotsuffix the rest of the system uses on Bottom Sheet, Service Item and Upload File. A consumer can now tell a slot from a frame by its name alone, across every component. (C1 · Rename) C1 · Layer Structure & Naming - All six icon sizes confirmed in use. v2.3:
XL | LG | MD | SM | XS | XXSstays at six by owner confirmation — each size appears in a real screen rather than being carried forward from the retired numeric scale. The 18-variant count is therefore the intended size of the set, not an artefact. (C2 · Property) C2 · Variant & Property Naming
Heading Goes Here
Label:Description goes here
Label:Description goes here
Label| Role | Element | Token | Value |
|---|---|---|---|
| Default | Background | — | #FFFFFF |
| Default | Border | — | #E5EBF4 |
| Default | Preamble | — | #005CE5 |
| Default | Tag | — | #D61B2C |
| Default | Title | — | #0A2757 |
| Default | Label | — | #90A8D0 |
| Default | Description | — | #445C85 |
| Default | Badge background | — | #E5F1FF |
| Default | Badge label | — | #005CE5 |
| Default | Leading icon placeholder | — | #D7E0EF |
| Default | Chevron | — | #005CE5 |
| Default · Disabled | Preamble | — | #9BC5FD |
| Default · Disabled | Title | — | #C2CFE5 |
| Default · Disabled | Label | — | #C2CFE5 |
| Default · Disabled | Description | — | #C2CFE5 |
| Default · Disabled | Badge background | — | #C2C6CF |
| Default · Disabled | Badge label | — | #FFFFFF |
| Default · Disabled | Leading icon placeholder | — | #9BC5FD |
| Default · Disabled | Chevron | — | #9BC5FD |
| Skeleton | Background | — | #FFFFFF |
| Skeleton | Placeholder bars | — | #EEF2F9 |
| Skeleton | Leading icon placeholder | — | #EEF2F9 |
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:card:1.0.0")
Import
import EastBlueDS import com.eastblue.ds.card.*
Status, State and IconSize are the set’s three variant properties. Leading-Icon-Slot, Trailing-Icon-Slot and Description are Figma Slots. Preamble and Title are text layers; Tag and Badge are nested Badge instances — all read from the layer tree of 5412:31505. Whether the text layers are exposed on the property panel is not readable with the plugin.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| Status — Default, Skeleton | isLoading: true | isLoading = true |
| State — Default, Disabled | .disabled(true) | enabled = false |
| IconSize — XL, LG, MD, SM, XS, XXS | .ebIconSize(.xl / .lg / .md / .sm / .xs / .xxs) | iconSize = EBIconSize.XL / LG / MD / SM / XS / XXS |
| Leading-Icon-Slot (slot) | .ebLeadingIcon(Image) | leadingIcon = { Icon(…) } |
| Trailing-Icon-Slot (slot) | .ebTrailingIcon(Image) — Chevron Right by default | trailingIcon = { Icon(…) } — Chevron Right by default |
| Description (slot) | .ebDescription(label: "Label", "…") once per row | descriptions = listOf("Label" to "…") |
| Preamble (text) | .ebPreamble("Blurb", tag:) | preamble = "…" |
| Title (text) | EBGenericCard("Heading Goes Here") | title = "…" |
| Tag (nested Badge instance) | .ebPreamble(_, tag: "Tag") | tag = "…" |
| Badge (nested Badge instance) | .ebBadge("Label") | badge = "…" |
EBGenericCard("Heading Goes Here")
.ebPreamble("Blurb", tag: "Tag")
.ebDescription(label: "Label", "Description goes here")
.ebDescription(label: "Label", "Description goes here")
.ebBadge("Label")
.ebLeadingIcon(Image(systemName: "star.fill"))
.ebIconSize(.xl) EBGenericCard(
title = "Heading Goes Here",
preamble = "Blurb",
tag = "Tag",
descriptions = listOf("Label" to "Description goes here", "Label" to "Description goes here"),
badge = "Label",
leadingIcon = { Icon(Icons.Filled.Star, null) },
iconSize = EBIconSize.XL
) EBGenericCard(isLoading: true)
.ebIconSize(.xl) EBGenericCard(
isLoading = true,
iconSize = EBIconSize.XL
) | Requirement | iOS | Android |
|---|---|---|
| Row as a button | Whole row in Button; one accessibilityLabel combining preamble, tag, title, descriptions and badge. | Modifier.clickable { onTap() }.semantics(mergeDescendants = true) on the row. |
| Reading order | Preamble, tag, title, each label and description pair, then badge — top to bottom as drawn. | Same order; TalkBack follows the composition order. |
| Disabled | .disabled(true) adds the .notEnabled trait. | enabled = false sets the disabled semantics. |
| Loading | Announce "Loading" once; .accessibilityHidden(true) on the placeholder bars. | contentDescription = "Loading" on the skeleton container; placeholders cleared with clearAndSetSemantics. |
| Dynamic type | Title 18 pt and the 12 pt rows scale with Dynamic Type; the row grows in height. | Text in sp; the row grows with the font scale. |
| Minimum target | 148 pt row ≥ 44 pt. | 148 dp row ≥ 48 dp. |
Do
Pick IconSize by list density — XL for a product catalog, XXS for a dense service menu.
Don't
Don’t mix icon sizes inside one list.
Do
Keep the Title to one line inside the 180 px content block.
Don't
Don’t let the title wrap under the tag — shorten it instead.
Do
Swap Leading-Icon-Slot for the product or service icon at 1:1.
Don't
Don’t ship the Placeholder instance to production.
Do
Use Status Skeleton while the list loads.
Don't
Don’t pair Skeleton with Disabled — the set does not build it.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Needs Refinement | PascalCase frames and three named slots on 5412:31505. A _space_40 spacer instance sits between ContentBlock and IconContainer — confirm it or replace it with padding. |
| C2 | Variant & Property Naming | Ready | Status, State and IconSize with Title Case values; no absence-based names. |
| C3 | Token Coverage | Ready | Title resolves to Primary/Headlines/Block, Preamble to Primary/Label/Small, Label and Description to Secondary/Bold/Caption. Colour variable bindings are not readable with the plugin — confirm in Dev Mode. |
| C4 | Native Mappability | Ready | Three enums, three slots, two text values and two Badge instances — maps 1:1 onto EBGenericCard. |
| C5 | Interaction State Coverage | Ready | Default and Disabled confirmed as the intended coverage (v2.2); Skeleton exists at State Default only. |
| C6 | Asset & Icon Quality | Ready | Chevron Right is a vector instance in Trailing-Icon-Slot; Leading-Icon-Slot holds a swappable Placeholder instance; Tag and Badge are Badge instances. |
2 Status × 2 State × 6 IconSize, Skeleton without Disabled = 18 variants
| IconSize | Variants | Count |
|---|---|---|
| XL | Default · Disabled · Skeleton | 3 |
| LG | Default · Disabled · Skeleton | 3 |
| MD | Default · Disabled · Skeleton | 3 |
| SM | Default · Disabled · Skeleton | 3 |
| XS | Default · Disabled · Skeleton | 3 |
| XXS | Default · Disabled · Skeleton | 3 |
View full IconSize × Status × State breakdown (18 rows)
| IconSize | Status | State | Node | Dimensions |
|---|---|---|---|---|
| XL | Default | Default | 5412:31505 | 360 × 148 |
| XL | Default | Disabled | 5418:32487 | 360 × 148 |
| XL | Skeleton | Default | 5412:31530 | 360 × 146 |
| LG | Default | Default | 5412:31541 | 360 × 148 |
| LG | Default | Disabled | 5418:32462 | 360 × 148 |
| LG | Skeleton | Default | 5412:31566 | 360 × 146 |
| MD | Default | Default | 5412:31577 | 360 × 148 |
| MD | Default | Disabled | 5418:32437 | 360 × 148 |
| MD | Skeleton | Default | 5412:31602 | 360 × 146 |
| SM | Default | Default | 5412:31613 | 360 × 148 |
| SM | Default | Disabled | 5418:32412 | 360 × 148 |
| SM | Skeleton | Default | 5412:31638 | 360 × 146 |
| XS | Default | Default | 5412:31649 | 360 × 148 |
| XS | Default | Disabled | 5418:32387 | 360 × 148 |
| XS | Skeleton | Default | 5412:31674 | 360 × 146 |
| XXS | Default | Default | 5412:31685 | 360 × 148 |
| XXS | Default | Disabled | 5418:32362 | 360 × 148 |
| XXS | Skeleton | Default | 5412:31710 | 360 × 146 |
Status — Default, Skeleton — with State and IconSize as panel controls; colours, text styles and the 148 / 146 heights read off 5412:31505 and 5412:31530. Padding, gap, alignment and colour tokens are not readable with the plugin and stay —.Status × State × IconSize schema. Property Mapping is one row per property with the three slots, Usage Snippets and Guidelines added, Scorecard regraded, Variants Inventory moved from the 12 numeric iconSize nodes to the 18 live nodes with a summary and full breakdown._space_40 (an instance carrying a Roboto "40" label) separates ContentBlock from IconContainer on 5412:31505. Confirm it is intentional or replace it with auto-layout padding. Filed from the scorecard for the Component Review.iconSize values moved from numeric to semantic; Disabled state added; skeleton moved onto its own Status axis; State and Status PascalCase, all values Title Case; The ⤷ glyph is gone and slots are kebab-cased; Legacy sigil dropped and frames moved to PascalCase; Skeleton variants brought onto the shared vocabulary; Two TextContainer rows confirmed intentional peers; State coverage confirmed as-is; iconSize → IconSize; Slot suffix aligned to the family convention; All six icon sizes confirmed in use.