East Blue
KeepReady
Segmented Control - Group Component link

A labeled Segmented Control with a slot below for helper text, avatars or other content.

Keep — all four traits pass
Nothing is open. A header and an exposed Segmented Control over a named Slot Container, shown by hasSlotContainer, that holds helper text, avatars or any content.
In Context

How the segmented control group appears in a real product screen — Split amount on Create KKB, with Equally / By Item, a subtext line and the avatars of the people splitting.

Segmented Control Group shown on the GCash Create KKB screen: the Split amount label, an Equally and By Item segmented control, the subtext The total bill will be split equally between, and three avatars JF, D and EM
Live Preview
Properties
hasSlotContainer
DS Health
Reusable
Pass
One component: whatever sits under the control — helper text, avatars, a count — goes in the named Slot Container, so no combination needs a variant.
Self-contained
Pass
Composes the FormGroup Header and Segmented Control instances; their own issues are tracked on their own pages, not carried here.
Consistent
Pass
Its one property is clean: hasSlotContainer is a boolean over a named slot. The states live in the Segmented Control Button instances inside.
Composable
Pass
The area under the control is a slot consumers fill with any content, and the inner Segmented Control is an exposed nested instance — its numberOfTabs (2, 3, 4) is set from the Group’s own panel.
Issues
  • No way to change avatar count: the avatars sit in a named Slot Container, which takes any number of avatars or other content — checked on node 26628:50778 (C4)
  • hasSubtext is a variant axis, not a boolean: the Group is now a single component and hasSubtext is a boolean bound to the Subtext Message — checked on node 26628:50786 (C1)
  • Inherited "No Focused state": withdrawn — Segmented Control Button ships Default, Pressed and Disabled by design; on touch screens focus is drawn by the system, so no Focused variant is needed — owner decision, 6 Oct 2026, node 26628:50752 (C5)
  • Inherited "segment labels are not text properties": moved to Segmented Control Button, where the label lives — node 26628:50752 (C4)
Recommendations
  • Promote inner control to a data-driven Segmented Control. Tracked on the Toggle - Segmented Control component — once that ships segments: [Segment], this group inherits the flexibility automatically. Family
  • Document the A11y model for the labeled group. Use aria-labelledby referencing the label. Subtext gets aria-describedby. Avatars below should be a separate accessible region with their own labels. A11y
  • Convert boolean axes to named slots. Applied — the subtext × avatars boolean axes are gone; the Group is one component and everything under the control goes in the named Slot Container (node 26628:50786). Slot
  • Adopt Figma Slot for both subtext and avatars. Applied — the Slot Container is a native Figma SLOT, empty by default, that takes a Subtext Message, an Avatar group or any other content without detaching (node 26628:50786). Slot

Component Playground

Layout

Typography

Colors

Installation Planned API

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

Import

import EastBlueDS
import com.eastblue.ds.toggle.*
Property Mapping

Read off the property panel of component 26628:50786: one boolean, hasSlotContainer, and one slot, Slot Container. Nothing else is exposed.

Figma PropertySwiftUICompose
hasSlotContainer — true, false presence of the .ebSlotContainer { } trailing closure slotContainer: @Composable (() -> Unit)? = null
Slot Container (slot) — empty by default; holds helper text, avatars or other content .ebSlotContainer { HStack(spacing: 2) { … } } slotContainer = { … }
SwiftUI
ios/Components/Toggle/EBSegmentedControlGroup.swift
Jetpack Compose
android/components/toggle/EBSegmentedControlGroup.kt
Usage Snippets Planned API
Label and control
// hasSlotContainer = false — header and control only, 312 × 62
EBSegmentedControlGroup(
    label: "Label",
    segments: ["Label", "Label"],
    selectedIndex: $selected
)
// hasSlotContainer = false — header and control only, 312 × 62
EBSegmentedControlGroup(
    label = "Label",
    segments = listOf("Label", "Label"),
    selectedIndex = selected
)
With subtext
// hasSlotContainer = true — the subtext goes in the slot, 312 × 102
EBSegmentedControlGroup(
    label: "Label",
    segments: ["Label", "Label"],
    selectedIndex: $selected
)
.ebSlotContainer {
    EBSubtextMessage("Use this space for your subtext.")
}
// hasSlotContainer = true — the subtext goes in the slot, 312 × 102
EBSegmentedControlGroup(
    label = "Label",
    segments = listOf("Label", "Label"),
    selectedIndex = selected,
    slotContainer = {
        EBSubtextMessage(message = "Use this space for your subtext.")
    }
)
With the slot container
// hasSlotContainer = true (the default) — Figma ships the slot empty;
// fill it with any content, here an Avatar Group.
EBSegmentedControlGroup(
    label: "Label",
    segments: ["Label", "Label"],
    selectedIndex: $selected
)
.ebSlotContainer {
    EBAvatarGroup(avatars: recentContacts)
}
// hasSlotContainer = true (the default) — Figma ships the slot empty;
// fill it with any content, here an Avatar Group.
EBSegmentedControlGroup(
    label = "Label",
    segments = listOf("Label", "Label"),
    selectedIndex = selected,
    slotContainer = {
        EBAvatarGroup(avatars = recentContacts)
    }
)
Accessibility
RequirementiOSAndroid
Label association Use .accessibilityLabel on the segmented row referencing the header text, so the control is never announced as a bare "Label, Label". Wrap in Modifier.semantics { contentDescription = label } on the group container.
Selection semantics The segments are a single-select set — .accessibilityAddTraits(.isSelected) on the active one, and announce position ("1 of 2"). Use Modifier.selectableGroup() on the row and selected = true on the active segment.
Minimum target The segment is 156 × 40pt. 40 is under the 44 × 44pt minimum — add 4pt of hit area above and below rather than resizing the control. Same geometry in dp; 40 is under the 48 × 48dp minimum, so extend the touch target without changing the drawn height.
Subtext announcement Wire as .accessibilityHint on the segmented row so VoiceOver reads "Label, hint: Use this space for your subtext." Use stateDescription on the row.
Slot container A separate accessibility region — the slot is arbitrary content and must carry its own labels, not inherit the group’s. Same. Give the slot its own contentDescription per child; do not merge it into the group semantics.
Dynamic Type / font scaling Header is 14pt and the segment labels 16pt — both must scale. The 40pt control height has to grow with them. Use sp for all three text layers and let the 40dp row expand with fontScale.
Usage Guidelines

Do

Use the group when a segmented control needs a heading — it bundles <code>FormGroup Header</code> and <code>Segmented Control</code> over a <code>Slot Container</code> at a fixed rhythm.

Don't

Don’t reach for it to get a bare segmented control. With no header to show, use <code>Segmented Control</code> directly — the group always renders its 22px header row.

Do

Put helper or validation copy in the <code>Slot Container</code> as a Subtext Message, and turn the slot off with <code>hasSlotContainer</code> when there is nothing to show.

Don't

Don’t put validation copy in the header. The header is <code>Primary/Label/Light/Small</code> at full-strength <code>#0A2757</code> and reads as a title, not a message.

Do

Use <code>Slot Container</code> for content that belongs to the selection — the recent-contact avatars it ships with are the reference case.

Don't

Don’t leave <code>hasSlotContainer</code> off and expect the avatars to show. It defaults to <code>False</code>, so the slot and its three Avatars are invisible until a consumer opts in.

Do

Set the segment labels and the selected index in code.

Don't

Don’t look for them in Figma. The nested <code>Segmented Control</code> is not exposed on the group, so segment count, labels and selection are all code-only — two segments is what the component draws.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Every part is a named instance — FormGroup Header, Segmented Control — and the slot is a real SLOT node named Slot Container.
C2 Variant & Property Naming Ready One property, hasSlotContainer, a boolean with the has prefix. The old split — hasSubtext as a variant beside a boolean — is gone with the subtext moving into the slot.
C3 Token Coverage Ready The group owns no colour of its own — every value belongs to a nested component or to the slot’s example content, so there is nothing here to leave unbound. Bindings themselves cannot be read with the Talk To Figma plugin, so the Style tab’s token column is — rather than asserted.
C4 Native Mappability Ready A vertical container with a header, a control, and two optional rows — one a string, one an arbitrary slot. Maps to a single view on both platforms. The named-slot restructure the 1.0.0 assessment asked for has landed in Figma.
C5 Interaction State Coverage Ready The group has no states of its own, which is correct. The inner Segmented Control is an exposed nested instance, and its segments carry Default, Pressed and Disabled.
C6 Asset & Icon Quality Not Applicable No assets owned by this component. The Avatars inside Slot Container are example content and belong to the Avatar component.
Variants Inventory (1 total)

No variant properties × 1 component = 1. hasSlotContainer is a boolean and Slot Container is a SLOT — both change what renders without adding a variant. Size below is as Figma ships it, with the empty slot showing.

ComponentNode IDSizeParts
Segmented Control - Group26628:50786312 × 102Header 22 + Segmented Control 40 + Slot Container 40
3.0.0 — October 2026Major
Rebuilt as a single component · node 26628:50786
Two variants became one component. The hasSubtext=true|false variant axis is gone; hasSubtext is now a boolean bound to the Subtext Message. The page moved from the deleted set 26628:50778 to 26628:50786.
C1 Resolved
DS Health re-rated. Reusable → pass. Consistent stays partial: the segments still have no Focused state and their labels are not properties.
Docs
Inherited "No Focused state" withdrawn; Consistent → pass. The states live in the Segmented Control Button instances — Default, Pressed and Disabled by design.
C5 Withdrawn
Inherited label issue moved to Segmented Control Button; Self-contained → pass. Nothing is open on the Group.
C4 Moved
The subtext moved into the slot. hasSubtext and the fixed Subtext Message are gone; the Slot Container, empty by default and shown by hasSlotContainer, now holds helper text, avatars or other content. C2 → ready.
C2 Resolved
Two recommendations applied. “Convert boolean axes to named slots” and “Adopt Figma Slot for both subtext and avatars” — both done by the slot rebuild.
Docs
Live Preview and Usage Snippets follow the slot rebuild. The preview has one control, hasSlotContainer (on, as Figma ships it), and draws the slot empty; the Selected, Subtext and Avatars controls are gone. The subtext snippet now passes EBSubtextMessage as slot content.
Docs
Property Mapping lists only the Figma properties. The two rows for the header label and the segments, which have no Figma property, are removed; the component exposes hasSlotContainer and Slot Container.
Docs
Composable → pass, verdict Fix → Keep. The inner Segmented Control is an exposed nested instance (isExposedInstance), so its numberOfTabs is set from the Group’s panel — it was never fixed. C5 → ready for the same reason; the states live in its segment buttons.
Docs
2.1.0 — October 2026Minor
Component Review against the current set · node 26628:50778
No way to change avatar count. the avatars sit in a named Slot Container, which takes any number of avatars or other content.
C4 Resolved
Boolean variant matrix instead of named slots. The avatars moved into a named Slot Container. Still open: hasSubtext is a variant axis, not a boolean.
C1 Partial
Inherits all Toggle - Segmented Control issues. Segment count and Pressed / Disabled are fixed in the inner control. Still open: Inherits two Segmented Control issues.
C5 Partial
DS Health re-rated against node 26628:50778. Reusable → partial, Consistent → partial, Composable → partial.
Docs
2.0.0 — September 2026Major
Rebuilt in Figma on new node 26628:50778
The component was rebuilt in Figma and never logged. The record still assessed 27:30940; the live set is 26628:50778. This entry is dated when the change was found, not when it was made — nothing in the file records the latter.
Rebuild
Figma Slots adopted. Slot Container is now a real SLOT node holding three 32px Avatars, replacing the baked-in avatar row. This applies the standing Adopt Figma Slot recommendation.
C1 Resolved
The boolean variant matrix collapsed. subtext × avatars produced four variants; the set now ships two — hasSubtext is the only variant axis, and hasSlotContainer is a boolean property that changes what renders without doubling the matrix.
C2 Resolved
Width corrected — 366 to 312. Every part of the component measures 312: header, control, subtext and slot alike.
Docs
Heights corrected. Documented as 64 / 86 / 104 / 118; read off Figma as 62 / 84 with the slot hidden and 102 / 124 with it shown. The parts are 22 + 40 + 22 + 40.
Docs
Gap is 0, not 8. The tab documented 8px between label, control and subtext. Every part is stacked flush — the rhythm you see is each nested instance’s own bottom padding, which means the group cannot tune its own spacing.
Docs
A Selected control was documented that Figma never had. The Style tab offered First / Second; the nested Segmented Control is listed under Nested instances and exposes nothing, so selection is code-only. The control is gone and Property Mapping now says so.
C4 Resolved
Style tab rebuilt to one card with the Figma property panel. hasSubtext and hasSlotContainer as toggles; Slot Container and the nested Segmented Control as static rows, since a slot takes no control.
Docs
Typography resolved to style names. Header Primary/Label/Light/Small, segment label Primary/Label/Base, subtext Secondary/Bold/Caption — all three matched. The tab previously wrote the header as a font spec, Proxima Soft Semibold · 16 / 16, at a size the layer does not use (it is 14 / 14).
C3 Resolved
Preview border radius fixed. The selected segment was drawn as a square rect clipped by a <clipPath>, the way Figma exports it — the clip did not resolve in injected markup, so the blue half rendered with square left corners. Redrawn as an explicit rounded path; verified in a browser.
Docs
Code tab rebuilt. Installation had no blocks at all and Usage Snippets and Usage Guidelines were empty. Property Mapping described the retired subtext=yes/no / avatars=yes/no axes, and the Variants Inventory listed four 27:309xx nodes at 366 wide.
Docs
Segment height is under the minimum touch target. The segment is 156 × 40 — below 44 × 44pt on iOS and 48 × 48dp on Android. The Accessibility table now says to extend the hit area rather than resize the control.
C5 Open
Two properties with the same job, built two ways. hasSubtext is a variant with true/false values while hasSlotContainer is a boolean property. Both toggle one row’s presence; only one doubles the variant count.
C2 Open
Nothing about the nested control is reachable. Segment count, labels, selection, pressed and disabled all live in Segmented Control, which the group does not expose — so every instance draws the same two segments with the first selected.
C5 Open
Token bindings unread. The Talk To Figma plugin returns no variable bindings, so all nine colour rows carry —. The previous record’s paths were not carried forward — they named the retired node.
C3 Open
meta.node still points at the retired 27:30940, and the Overview tab’s four open issues ask for changes this rebuild already made. Both are Overview scope and need a Component Review run — until then the Code tab and the Overview tab disagree.
Docs
1.0.0 — May 2026Major
Initial Assessment · node 27:30940
Component assessed — 4 variants on subtext × avatars. Composes Toggle - Segmented Control + Subtext Message + Avatar. Documented
Initial
Verdict: Fix — Replace boolean axes with named optional slots (subtext?, avatars?). Drops variant count from 4 to 1 and unlocks any future trailing content. Open
Family
C1 — Boolean axes — Slot-presence as variants. Convert to Figma Slot. Open
C1
C5 — Inherited state gaps — Toggle - Segmented Control is missing Pressed / Focused / Disabled; this group inherits all of those. Open
C5
C7 — Code Connect — Not registered. Blocked on slot restructure. Open
C7