East Blue
KeepReady
Select Group Component link

The menu surface a Select opens — a rounded card wrapping a slot of Select Items, with optional dividers between them.

Keep — the cleanup is done
All four DS Health traits pass and nothing is open. BorderType and its values and the ⤷ SelectionSlot follow the naming guidelines, and the Scrollbar frame is intentional.

How the select group appears in real product screens — the list of options that opens under an expanded Select (the GCredit limit on Account Details), next to a closed Select on Scheduled Transfer.

Select Group component shown on two GCash screens: an expanded Select on Account Details listing I don't have GCredit and peso amounts from 1,000 to 6,000, and a Scheduled Transfer screen with a closed Repeat every Select set to Saturday
Text
Text
Text
Text
Text
Text
Text
Properties
BorderType
Density
Reusable
Pass
One surface for every Select in the product, and usable on its own wherever a list of options needs a card around it.
Self-contained
Pass
Owns the card — the white fill, the hairline, the 6px radius and the padding around the rows. It does not draw the rows themselves.
Consistent
Pass
BorderType and its values are joined after this pass, the MIddle Inset typo is gone, and the slot carries the family's ⤷ prefix as ⤷ SelectionSlot.
Composable
Pass
A real Figma SLOT filled with Select Item instances, and consumed as a whole by Select. What it cannot do is apply its own properties to substituted content — see the resolved list.
State iOS Android Figma Property Notes
BorderType=MiddleInset N/A N/A inset dividers Dividers sit between rows, inset from both edges. The default the component ships with.
BorderType=FullWidth N/A N/A edge-to-edge dividers Same dividers, running the full width of the card.
BorderType=None N/A N/A no dividers The divider nodes are absent entirely, which is why these variants are 24 shorter than their bordered siblings.
Density N/A N/A 312 / 368 / 424 Sets the row height of the Select Items in the default content — 40, 48 and 56 respectively — which is what drives the card's height.
⤷ SelectionSlot N/A N/A 7 rows by default Ships with seven Select Items and a hidden Label. Substituted content replaces all of it, dividers included.
  • The MIddle Inset typo is gone. The value carried a capital I in the middle of the word. It is now MiddleInset — and joined at the same time, so the property reads BorderType = MiddleInset | FullWidth | None. C2 · Variant & Property Naming
  • The vestigial Dropdown Item - Last name is gone. The final row in the slot was still called Dropdown Item - Last, left over from before the Dropdown to Select rename — and confusingly its instance had been renamed while the master had not, so the two disagreed depending on where you looked. It is now simply Select Item: seven rows with six dividers between them, and no trailing divider to suppress. C1 · Layer Structure & Naming
  • The hidden 366px leftovers are gone. Five stale Select Item instances sat in the slot at 366 × 52 with no children, all stacked at a single coordinate. They were invisible but shipped inside every variant. Removed. C1 · Layer Structure & Naming
  • The slot is named for what belongs in it. It was Slot — the arrow prefix arrived first, then the name. ⤷ SelectionSlot now says what the container is for, rather than just marking it as a slot. C2 · Variant & Property Naming
  • Both properties shape the default content rather than being applied to the slot. Each of the nine variants contains its own hand-placed rows: MiddleInset and FullWidth interleave six Horizontal / Divider siblings between seven Select Items, and None simply has no divider nodes at all. So a consumer who fills the slot with their own rows loses both the dividers and the density, because those lived in the default content they replaced. This is the same Figma limit the Slider hit — a component property cannot reach into a slot — and it is understood and already instructed to designers. Recorded here because natively both are list-level properties applied to whatever rows the list holds, which is a genuine divergence a developer needs told rather than left to infer. C4 · Native Mappability
  • The hidden Label is a subsection header. A 320 × 28 instance at the top of the slot, hidden in all nine variants. Confirmed as intentional: it acts as a divider or subsection heading for grouped option lists, and stays hidden by default because most lists are flat. C1 · Layer Structure & Naming
  • The Scrollbar frame stays. A hand-drawn 12 × full-height rectangle inside the slot, hidden. Native platforms render their own scroll indicator, so this has no counterpart in code — kept deliberately as a design-time affordance, consistent with how the same call was made elsewhere in the system. C4 · Native Mappability
  • Pass the Divider component's name upstream. The dividers are instances of a component published as Horizontal / Divider. Two things are off, neither of them this component's to fix. The spaces around the slash are inconsistent with how the rest of the file namespaces — Primary/Label/Large, Flags Library - 16px — and the order is inverted: it produces a folder named Horizontal containing Divider, and presumably a Vertical folder containing another Divider, splitting one family across two folders named for the wrong thing. Divider/Horizontal would group them. Docs
  • Write down what a slot swap costs. Because the dividers and the density live in the default content, replacing the slot's contents silently drops both. Designers have been briefed, but the constraint is invisible from the component — it belongs in the usage documentation where someone reaches for it, and in the native contract where it does not apply at all. Docs
  • Give the Label a way to be switched on. It is a real subsection header sitting hidden in every variant. Revealing it currently means finding a hidden layer inside a slot. A boolean on the group — or shipping it as an option inside the slot's default content — would make it discoverable rather than folklore. Property
  • See siblings: Select Item fills the slot, and Select is the control that opens this surface. Select Field is the trigger, deliberately outside this family's scope. Family
  • Publish the token names once Dev Mode is read. v2.0.1: Applied — all four colours the group paints are named. bg/color-bg-main on the card, border/color-border-weak on both its hairline and the dividers, and border/color-border on the hidden scrollbar. Read against the rest of the system rather than from Dev Mode, which still returns variable IDs instead of names. Everything inside a row belongs to Select Item, which is why those are named rather than valued. Token

Component Playground

Layout

Typography

Colors

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:select:1.0.0")

Import

import EastBlueDS
import com.eastblue.ds.select.*

Three properties. Two native parameters have no row because neither is one: header would drive the Label layer at the top of the slot, and the scrollbar is a hidden frame — both ship hidden as options held in reserve, and on both platforms the scrollbar is drawn by the platform rather than by the component. The one thing that does not map cleanly is how the two properties reach the rows. In Figma BorderType and Density shape the slot’s default content; a component property cannot reach inside a slot, so replacing the rows leaves both properties with nothing to act on. Natively they apply to whatever fills the slot, which is the behaviour the page documents and the better one — but it means the Figma component and the native component differ in mechanism, not just in syntax.

Figma PropertySwiftUICompose
BorderType — None, MiddleInset, FullWidth borderType: EBBorderType borderType: EBBorderType
Density — Compact, Default, Comfortable density: EBDensity = .compact density: EBDensity = Compact
⤷ SelectionSlot (slot) @ViewBuilder content: () -> Content content: @Composable ColumnScope.() -> Unit
EBSelectGroup(borderType: .none) {
    EBSelectItem(label: "Savings")
    EBSelectItem(label: "Checking")
}
EBSelectGroup(borderType = EBBorderType.None) {
    EBSelectItem(label = "Savings")
    EBSelectItem(label = "Checking")
}
EBSelectGroup(borderType: .middleInset) {
    EBSelectItem(label: "Savings")
    EBSelectItem(label: "Checking")
}
EBSelectGroup(borderType = EBBorderType.MiddleInset) {
    EBSelectItem(label = "Savings")
    EBSelectItem(label = "Checking")
}
EBSelectGroup(borderType: .fullWidth, density: .comfortable) {
    EBSelectItem(label: "Philippines")
    EBSelectItem(label: "Singapore")
}
EBSelectGroup(borderType = EBBorderType.FullWidth, density = EBDensity.Comfortable) {
    EBSelectItem(label = "Philippines")
    EBSelectItem(label = "Singapore")
}
RequirementiOSAndroid
Exposed as a list of options .accessibilityElement(children: .contain) with a radio-group trait Modifier.selectableGroup() on the column
Announced when it opens Focus moves into the list; the Select's label carries the context paneTitle on the popup
Dividers are decorative .accessibilityHidden(true) contentDescription = null
Subsection header reads as a heading .accessibilityAddTraits(.isHeader) when the Label is shown Modifier.semantics { heading() }
Long lists stay reachable Scrolls to the selected row when opened Same — the selected row is brought into view

Do

Let the group own the dividers between rows.

Don't

Don't add dividers inside a Select Item — BorderType already places them.

Do

Re-apply density when you replace the slot's contents.

Don't

Don't assume swapped-in rows inherit the group's density — they don't, in Figma.

Do

Use None when the rows already read as separate cards.

Don't

Don't stack dividers against a row that draws its own border.

Do

Show the Label when a list has genuine sections.

Don't

Don't use it as a title for the whole menu — that belongs on the Select.

ID Criterion Status Notes
C1 Layer Structure & Naming Ready The vestigial Dropdown Item - Last and the five hidden 366px leftovers are both gone. The hidden Label is confirmed as an intentional subsection header.
C2 Variant & Property Naming Ready BorderType = MiddleInset | FullWidth | None after the typo fix and the joining, and the slot is ⤷ SelectionSlot.
C3 Token Coverage Ready Every colour is bound and named: bg/color-bg-main on the card, border/color-border-weak on both its hairline and the dividers, and border/color-border on the hidden scrollbar. Everything inside a row belongs to Select Item, which is why the rows are named rather than valued here.
C4 Native Mappability Ready A card wrapping a list maps directly. The one divergence is mechanical rather than cosmetic: in Figma BorderType and Density shape the slot’s default content, so replacing the rows leaves both with nothing to act on; natively they apply to whatever fills the slot. The native behaviour is the better one, and it is what the mapping documents — but the two components differ in mechanism, so the native binding will not be a straight pass-through.
C5 Interaction State Coverage Not Applicable The surface has no states of its own. Pressed and selected belong to the rows inside it.
C6 Asset & Icon Quality Ready No artwork of its own. The Label header and the Scrollbar are both hidden by default and confirmed intentional — options for when a group needs a heading or a list overflows, which most do not.

3 BorderType × 3 Density = 9. Row count is not an axis — the slot ships seven and takes whatever replaces them.

BorderTypeDensityRow heightCard heightNode
MiddleInsetCompact403127947:111631
FullWidthCompact403127947:111659
NoneCompact402887947:111687
MiddleInsetDefault483687947:111709
FullWidthDefault483687947:111737
NoneDefault483447947:111765
MiddleInsetComfortable564247947:111787
FullWidthComfortable564247947:111815
NoneComfortable564007947:111843
Token-names recommendation closed — node 7947:111630
The token-names recommendation was already satisfied and still read as outstanding. v2.0.1 named all four colours the group paints and C3 moved to Ready, but the recommendation asking for them stayed in the open list — the page asking for something it had already published. Moved to Applied, where the entry records that they were read against the rest of the system rather than from Dev Mode, which still returns variable IDs instead of names.
Docs
Style and Code tabs rebuilt to the content guides — node 7947:111630
One card became three. BorderType is the driving property, so None, MiddleInset and FullWidth each get a card. Density stays a control, and ⤷ SelectionSlot is a slot, which leaves the panel with one control for a component of nine variants.
Docs
The three border treatments are read from the artwork rather than described. MiddleInset is a 320 × 4 frame holding a hairline from x=12 to x=308; FullWidth runs 0 to 320; None has no divider layers at all. That last one is the whole of the 24px height difference — six gaps × the 4px frame each divider sits in — which had read as an unexplained number.
Docs
Vertical padding was documented as 0 and is 4. Every height now reconstructs from the seven Layout keys alone: 7 rows × 40, plus six 4px divider frames where the version has them, plus 4 above and 4 below. 288 and 312 at Compact. The old rows were 8px short of their own stated heights, which is the check that caught it. Width also read a bare 320 where the panel says Fill — 320 is only what it resolves to here.
Docs
The leading mark was a circle where the gear teeth belong. The preview drew an outer circle and an inner circle. It is now the artwork exported from the Leading Element at node 7947:111971 — a stroked gear outline over a 40%-opacity inner dot — in currentColor, so the selected row’s blue reaches it through one declaration instead of three brittle selectors. Select Item now draws the same block, so both pages render this nested instance identically.
Docs
The preview shows the first row selected, as Figma ships it. That is what the component draws, and it shows how a Select Item’s selected treatment reads inside the group — label and leading mark both stepping to text/color-text-primary while the row keeps its surface.
Docs
Both text styles resolve. The row label is Primary/Multi-line Label/Light/Base and the hidden group header is Primary/Label/Light/Small, replacing font specs and a "names pending Dev Mode read" IOU.
C3 resolved
All four colours the group paints are named. bg/color-bg-main on the card, border/color-border-weak on both its hairline and the dividers, border/color-border on the scrollbar. Everything inside a row belongs to Select Item, so the rows are named rather than valued — and a Colors by BorderType table now carries the lot.
C3 resolved
Two hidden layers are recorded as deliberate, not missing. The Label header and the Scrollbar both ship hidden — options for when a group needs a heading or a list overflows, which most do not. Written into the Properties rows, the colours table and C6, because a documented scrollbar colour that never appears reads as a defect otherwise.
Docs
The preview drew in the documentation font. .eb-preview-sgroup declared font-family: inherit, which resolves to BarkAda; every visible layer is a Select Item row and the hidden header is Primary/*, all Proxima Soft. Caught by the typeface check rather than by eye.
Docs
The preview drew five rows where the component holds seven. Every height on the Layout section is built from seven — 7 × 40 plus 4 above and 4 below is 288 at None, and the six divider frames make it 312 for the other two — so a five-row preview quietly contradicted the numbers printed beside it. Found while composing the same instance into Select, whose Expanded versions draw the group at its full 312.
Docs
Each divider is a 4px frame, not a 1px line. The preview drew a 1px CSS border between rows, leaving it 18px short of the 312 it documents — and the inset was faked with a margin that stripped the horizontal padding from every row after the first, so only the selected row had a full-width surface. Both are now drawn the way Figma builds them: a 4px gap carrying a hairline, inset 12 either side at MiddleInset and edge to edge at FullWidth. This is the same 24px that separates None from the other two, and the preview now reconstructs it rather than asserting it.
Docs
The install block pointed at coordinates that will never exist. gcash/east-blue-ios and com.gcash.eastblue:components:1.0.0, with no Import line. It now cites the Select family artifact com.eastblue.ds:select:1.0.0 and imports com.eastblue.ds.select.*.
Docs
Property Mapping listed two hidden layers as properties. Label (hidden) and Scrollbar are not in the panel. Three rows now, one per property, with header and the scrollbar explained in the description — the scrollbar because on both platforms it is drawn by the platform rather than by the component.
Docs
Every snippet called EBSelectItem a way its own page does not. One used an unlabelled leading string, one used leading: — the parameter renamed to type: during Select Item’s own pass — and one passed header: to the group. A parent quoting a stale child API is invisible until the child is fixed, and nothing checks for it: each page validates fine alone.
Docs
Usage Snippets were keyed to use-cases, and one was not a BorderType at all. "Inset dividers (default)", "No dividers, comfortable rows" and "With a subsection header". One per value now, and the unverifiable "(default)" is gone — the panel lists None first.
Docs
C3 was still holding an IOU. Its note read "names need a Dev Mode read before they can be printed"; all four tokens are named. C6 now records both hidden layers as intentional, and C4 keeps the mechanism divergence in sharper terms.
Docs
The properties reach the rows differently in Figma and natively, and that is carried forward as a known divergence. In Figma BorderType and Density shape the slot’s default content — a component property cannot reach inside a slot, so replacing the rows leaves both with nothing to act on. Natively they apply to whatever fills the slot, which is the better behaviour and the one the page documents. It is not fixable in Figma, so a Code Connect binding will not be a straight pass-through.
Docs
Code Connect emptied, and DEV code is live for the first time. The demo script had no getSnippet, so both language tabs were frozen on a static string; they now show the slot as a trailing closure, which is the honest shape given the rows arrive through it rather than as a parameter.
Docs
Slot rebuild + reassessment on the 2026 Working File · node 7947:111630
Covers two changes at once. The slot rebuild that turned this from a hardcoded preview artifact into a real container — and withdrew a Consolidate verdict — was described in the component's verdict but never given a changelog entry of its own. This version records it alongside the cleanup that followed.
Rebuilt
The MIddle Inset typo is gone — the property and its values are now joined as BorderType = MiddleInset | FullWidth | None.
C2 resolved
The vestigial Dropdown Item - Last is gone. Its master and instance had disagreed since the Dropdown to Select rename; the last row is now simply Select Item, with six dividers between seven rows and none trailing.
C1 resolved
Five hidden 366px leftovers removed — stale Select Item instances stacked at one coordinate inside the slot, invisible but shipping in every variant.
C1 resolved
Slot renamed to ⤷ SelectionSlot, so the name says what belongs in it rather than only marking it as a slot.
C2 resolved
The Scrollbar frame kept, confirmed as an intentional design-time affordance with no native counterpart. The hidden Label confirmed as a subsection header for grouped lists.
C4 resolved
Both properties documented as shaping the slot's default content rather than applying to substituted content — a Figma limit, already instructed to designers, and a genuine divergence from the native contract where both are list-level.
C4 resolved
Node moved from 25783:1255 (Sticker Sheets v2) to 7947:111630 (2026 Working File), and navGroup changed from Dropdown to Select.
Rebuilt
Initial Assessment · node 6383:3446
Component assessed — Single variant, 8-item fixed layout at 366px. Rounded 6px card, white bg, 6px/12px drop shadow. All tokens bound (bg, radius, shadow, space). Documented
Initial
Last row is a detached frame — Node 6383:3442 is a hand-built Dropdown - Item frame instead of a DropdownItem component instance. Breaks consistency. Open
C1 Open
No slot, no item count, no fill-container width — Layout is hardcoded to 8 rows at 366px. Cannot be reused for menus of different sizes. Open
C1 Open
Irregular component name — "Dropdown Item - Group" uses a " - " separator inconsistent with other DS group names. Recommend rename to "Dropdown Menu". Open
C2 Open
Popover surface not a native primitive — Both Menu (iOS) and DropdownMenu (Compose) draw the shadowed card automatically. This component has no 1:1 native mapping. Open
C4 Open
Code Connect not registered — No properties to map. Consolidate into Dropdown before mapping. Open
C7 Open