A single selectable row inside a Dropdown popover — label, optional leading icon, and selected/disabled states.
disabeld (C2). Country variant uses a raster PNG flag (C6). No pressed/focused state variants (C5). Disabled is modeled as a type value rather than an orthogonal state (C4).Dropdown Item is the row primitive consumed by the Dropdown overlay and by Dropdown Item Group. Not used standalone.
Flags Library rather than baking in an asset.disabeld typo is gone — disabled is now an orthogonal State value (Default / Pressed / Disabled) that composes with any Type. isSelected is a clean two-value boolean on true/false, matching the C2 rule and Radio Button.Flags Library - 16px) rather than a hardcoded raster, so any locale works, and the three named element slots let consumers compose badges, checkmarks, or values without forking the component.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Default (unselected) | Yes | Yes | selected=false | Label #0A2757, bottom border #E5EBF4. |
| Selected | Yes | Yes | selected=true | Label #005CE5 (brand), same divider. No background highlight — relies on text color alone. |
| Disabled | Yes | Yes | type=disabeld | Soft fill #F6F9FD, label #C2CFE5. Modeled as a content type rather than a state (C4). |
| Pressed | No | No | — | Not defined. iOS highlight and Android ripple will have to be improvised at instance level. |
| Focused | No | No | — | Not defined. Required for keyboard / D-pad navigation in dropdown overlays. |
- Code Connect mappings not registered. Previously blocked by the enum typo, missing states, and raster flag — all resolved in the Select Item rebuild. Registration is now unblocked, but the SwiftUI / Compose mappings are not yet wired. C7 · Code Connect Linkability
- v2.0: Enum typo
disabeldfixed — disabled is now a correctly-spelledStatevalue, not a misspelled content type. (C2) - v2.0: Disabled promoted to an orthogonal
Stateaxis (Default / Pressed / Disabled) — composes with any Type, so "Peso Sign + Disabled" and "Flag + Disabled" are now expressible. (C4) - v2.0: Country flag replaced with a vector instance — now maps to
Flags Library - 16pxin the Leading slot, locale-swappable rather than a baked-in Philippines PNG. (C6) - v2.0: Pressed state added —
State=Pressedcovers touch feedback (iOS highlight / Android ripple). Focused is N/A on mobile. (C5) - v2.0: Selected affordance added —
isSelected=Yesflips the label to brand#005CE5and exposes a checkmark via the Trailing icon slot, no longer relying on label color alone. (C5) - v2.1:
isSelectedcollapsed to a clean two-value boolean — the ambiguous thirdDefaultvalue was removed and the prop now applies uniformly across all three Types. Coverage is consistent at 9 × unselected + 3 × selected per Type, and the selection prop maps 1:1 to a nativeBool. (C2) - v2.1:
isSelectedvalues renamedYes/No→true/falseacross all 36 variants — Select Item now matches the documented C2 rule and the rebuilt Radio Button. The system is on a single boolean vocabulary. (C2) - v2.1:
State=Disabled+isSelected=truedeliberately omitted — reviewed and confirmed intentional. A disabled row offers the user no action, and when the parent Select is disabled the menu never opens, so the combination is unreachable. (C5) - v2.1:
State=Pressed+isSelected=truedeliberately not modelled — reviewed and confirmed correct. Pressed is the act of choosing, so the meaningful case is pressing an unselected row, which ships. On native, pressed is not an authored state but an overlay the platform applies (SwiftUIconfiguration.isPressed, ComposeInteractionSource), so pressed-over-selected composes at runtime without a dedicated variant. Authoring the 9 would specify nothing a developer does not already get. (C5)
- Register Code Connect mapping to
EBSelectItem. With the rename, orthogonal state axis, and vector flag all shipped, wire the Figma properties (Type, Density, State, isSelected + the Leading / Content / Trailing slots) 1:1 to the SwiftUI / Compose API. Docs
- Renamed
disabeld→disabled. v2.0: Applied — disabled is now a correctly-spelledStatevalue. Rename - Promoted disabled to its own axis. v2.0: Applied —
State(Default / Pressed / Disabled) is orthogonal to Type, so disabled composes with Icon / Peso Sign / Flag. Property - Replaced the raster flag with a vector flag slot. v2.0: Applied — the Leading slot now carries a
Flags Library - 16pxvector instance, locale-swappable. Slot - Added a pressed state. v2.0: Applied —
State=Pressedmaps touch feedback to a tokenized background. Focused is N/A on mobile. State - Added an explicit selected-visual affordance. v2.0: Applied — brand-color label plus a checkmark via the Trailing icon slot on
isSelected=Yes. State - Generalized the amount variant around slots. v2.0: Applied — the peso sign is a Leading vector slot (
Peso Sign - Proxima) with the value in Content and a free Trailing slot for a badge or value. Slot - Collapsed
isSelectedto a clean boolean. v2.1: Applied — the thirdDefaultvalue is gone;isSelectedis a true two-value boolean across every Type, with consistent coverage. Property - Standardise the boolean vocabulary on
true/false. v2.1: Applied —isSelectedvalues renamedYes/No→true/falseacross all 36 variants, matching the C2 rule and Radio Button. The system is now on one boolean vocabulary. Rename
Plain text row. Default content type used by Dropdown. Label switches from neutral #0A2757 (default) to brand #005CE5 (selected).
All color roles are bound to the main/dropdown-item/color/* token family. Dropdown Item has no variable modes — colors are keyed by state only.
| Role | Token | DEFAULT | SELECTED | DISABLED |
|---|---|---|---|---|
| Row bg | main/dropdown-item/color/{state}/bg | transparent | transparent | #F6F9FD |
| Label | main/dropdown-item/color/{state}/label | #0A2757 | #005CE5 | #C2CFE5 |
| Bottom border | main/dropdown-item/color/{state}/border | #E5EBF4 | #E5EBF4 | #E5EBF4 |
| Peso sign (amount) | main/dropdown-item/color/{state}/icon-currency | #0A2757 | #005CE5 | – |
| Badge bg (text with tag) | main/badge/negative/heavy/background | #D61B2C | #D61B2C | – |
| Badge label (text with tag) | main/badge/negative/heavy/label | #FFFFFF | #FFFFFF | – |
Row with a trailing Badge instance (Negative/Heavy variant in stock). Used when an option needs an inline status label.
| Role | Token | Token |
|---|---|---|
| Row width | 366px (fill) | — |
| Row height | 50px (text / text with tag) · 51.2–52px (amount / country) | — |
| Padding top/bottom | 16px | space/space-16 |
| Padding left | 12px | space/space-12 |
| Padding right | 16px | space/space-16 |
| Gap (country / text with tag) | 8px | space/space-8 |
| Flag size (country) | 25 × 16 | — |
| Flag radius | 2px | radius/radius-1 (approx) |
| Peso sign size (amount) | 18 × 18 | — |
| Bottom border | 1px solid | main/dropdown-item/color/{state}/border |
| Row corner radius | 0 | radius/radius-0 |
Peso sign (vector, Proxima-sized) + amount text. Icon currency token flips to brand on selected.
| Role | Token | Font | Size | Tracking | Line-height |
|---|---|---|---|---|---|
| Label (all types) | Primary/Label/Light/Large | Proxima Soft Semibold | 18px | 0.25px | 18px |
| Amount text | Primary/Label/Light/Large | Proxima Soft Semibold | 18px | 0.25px | 18px |
| Badge label (text with tag) | Primary/Label/Fine | Proxima Soft Bold | 12px | 0.5px | 12px |
Leading flag (25 × 16, 2px radius) + country name and dial code. <strong>Flag is a raster PNG</strong>, not a vector instance — open issue (C6).
Soft fill row with muted label. Currently only exists at <code>selected=false</code>. <strong>Enum value is misspelled (<code>disabeld</code>)</strong> — open issue (C2).
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:dropdown:1.0.0") }
Import
import EastBlueDS // SwiftUI import com.eastblue.ds.dropdown.* // Compose
Dropdown Item is bundled with the Dropdown package. The planned SwiftUI API exposes it as EBDropdownItem; on Compose it maps to Material 3's DropdownMenuItem with EB-styled content.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| type = text | EBDropdownItem(label:) | DropdownMenuItem(text = { Text(label) }) |
| type = text with tag | EBDropdownItem(label:tag:) | trailing = { EBBadge(…) } |
| type = amount | .ebStyle(.amount) | style = EBDropdownItemStyle.Amount |
| type = country | EBDropdownItem(flag:name:dialCode:) | leadingIcon = { FlagIcon(…) } |
| type = disabeld Typo | .disabled(true) | enabled = false |
| selected = true | isSelected: Bool | selected: Boolean |
| selected = false | isSelected: Bool (default) | selected: Boolean (default) |
EBDropdownItem("Dropdown Item") .isSelected(category == "item") .onTap { category = "item" }
EBDropdownItem( label = "Dropdown Item", selected = selected == "item", onClick = { selected = "item" } )
EBDropdownItem("Dropdown Item") { EBBadge("Label", level: .heavy, state: .negative) }
EBDropdownItem( label = "Dropdown Item", trailing = { EBBadge("Label", level = EBBadgeLevel.Heavy, state = EBBadgeState.Negative) } )
EBDropdownItem(amount: "1,000.00") .ebStyle(.amount)
EBDropdownItem( label = "1,000.00", style = EBDropdownItemStyle.Amount )
EBDropdownItem( flag: Image("flag_ph"), name: "Philippines", dialCode: "+63" )
EBDropdownItem( label = "Philippines +63", leadingIcon = { FlagIcon(CountryCode.PH) } )
| Requirement | iOS | Android |
|---|---|---|
| Minimum touch target | 44 × 44 pt (row is 50pt tall) | 48 × 48 dp |
| Accessibility label | .accessibilityLabel("Philippines, +63") | contentDescription = "Philippines, +63" |
| Role | .accessibilityAddTraits(.isButton) | Role.Button |
| Selected announcement | .accessibilityAddTraits(.isSelected) | selected = true in semantics |
| Disabled announcement | .disabled(true) → VoiceOver says "dimmed" | enabled = false → TalkBack says "disabled" |
Do
Consume Dropdown Item through Dropdown or Dropdown Item Group. Keep labels short — 18px Semibold is the only supported text size.
Don't
Don't use Dropdown Item outside a dropdown overlay. For standalone list rows, use the List Item component instead.
Do
Pair selected state with a trailing checkmark (once added) so the picked item is unambiguous, especially on the country and amount variants.
Don't
Don't rely on the disabeld type to build disabled versions of amount or country — it only ships for the text content type. Use the planned disabled state axis instead.
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Ready | Semantic names: container, name, offset, Peso Sign - Proxima, Field Trailing Flag, philippines. |
| C2 | Variant & Property Naming | Requires Rework | Enum value disabeld is misspelled and ships into the generated TS type. |
| C3 | Token Coverage | Ready | All colors bound to main/dropdown-item/color/*; space + radius + typography tokens all present. |
| C4 | Native Mappability | Needs Refinement | Maps to a custom SwiftUI row / Material 3 DropdownMenuItem. Disabled should be an orthogonal prop, not a type value. |
| C5 | Interaction State Coverage | Requires Rework | No pressed or focused variants. Selected state relies on label color alone — no checkmark or background fill. |
| C6 | Asset & Icon Quality | Requires Rework | Country variant uses a raster PNG flag, not a vector instance. Peso sign is a vector via Peso Sign - Proxima. |
| C7 | Code Connect Linkability | Needs Refinement | No CLI mappings registered yet; blocked by C2, C5, C6. |
| Aspect | Status | Notes |
|---|---|---|
| Property naming | Requires Rework | Rename disabeld → disabled before registering |
| Asset quality | Requires Rework | Replace raster PH flag with vector flag slot |
| State coverage | Requires Rework | Pressed / focused variants missing |
| Native component file | Needs Refinement | EBDropdownItem.swift / EBDropdownItem.kt not yet created |
5 type values × 2 selected values = 10 theoretical slots, but disabeld only ships with selected=false, giving 9 actual variants.
| type | selected | Node ID | Notes |
|---|---|---|---|
| text | false | 23:199454 | Neutral label |
| text | true | 23:199456 | Brand label |
| text with tag | false | 883:29328 | Trailing Badge instance |
| text with tag | true | 883:30370 | Brand label + Badge |
| amount | false | 23:199458 | Peso sign + "X,XXX.XX" |
| amount | true | 23:199465 | Brand peso + brand label |
| country | false | 23:199472 | Raster flag + "Philippines +63" |
| country | true | 23:199476 | Brand "Philippines +63" |
| disabeld Typo | false | 883:30386 | Soft fill + muted label |
isSelected collapsed to two values — the ambiguous third Default value was removed; the prop now applies uniformly across Icon, Peso Sign, and Flag. Coverage is consistent at 9 × unselected + 3 × selected per Type, and selection maps 1:1 to a native Bool.
ResolvedisSelected values renamed Yes/No → true/false across all 36 variants. Select Item now matches the documented C2 rule and the rebuilt Radio Button; the system is on a single boolean vocabulary.
Resolveddisabeld resolved — disabled is now a correctly-spelled orthogonal State value, not a misspelled content type. Composes with any Type (e.g. Peso Sign + Disabled), resolving the earlier C2 + C4 issues.
ResolvedFlags Library - 16px in the Leading slot, locale-swappable rather than a baked-in Philippines PNG.
ResolvedState=Pressed covers touch feedback (focused is N/A on mobile), and isSelected=Yes flips the label to brand color plus exposes a checkmark via the Trailing icon slot.
ResolvedPeso Sign - Proxima) with the value in Content; the trailing slot stays free for a badge or value.
ResolvedisSelected exposes three values (No / Yes / Default) — ambiguous for a boolean and inconsistent across Types. Collapse to Yes/No.
Opendisabeld misspelled — ships into the generated TS type. Rename to disabled.
Openselected on/off and a pseudo-disabled content type. Touch/keyboard feedback unmodeled.
Opentype value — collides with content types (text / amount / country). Should be an orthogonal disabled axis.
Open