East Blue
KeepReady
Segmented Control Button Component link

A single tappable segment inside a Segmented Control. 6 variants across State (Default / Pressed / Disabled) × Active (true / false). Active segments fill brand-blue; inactive segments carry a brand-blue outline. (Assessed in the 2026 Working File.)

Keep — a complete state matrix
All four DS Health traits pass and nothing is open. Six variants cover State × isActive, including pressed-active, with token-bound fills and a token-bound outline on inactive segments.
In Context

How the segmented control button appears in a real product screen — the two segments Repeat (active) and Schedule Once under Set Autoload Schedule.

Segmented Control Button shown on the GCash Autoload screen: an active filled Repeat segment beside an outlined Schedule Once segment under Set Autoload Schedule
Live Preview
ActiveInactive
Properties
StateDefault · Pressed · Disabled
Activetrue · false
DS Health
Reusable
Pass
The one segment primitive for every Segmented Control — the row composes N instances at any count, so a single atom covers 2-, 3-, and 4-segment controls.
Self-contained
Pass
Owns its own fill, outline, label, and radius tokens across all six State × Active combinations. No external instance dependencies.
Consistent
Pass
Complete 3 × 2 matrix — State (Default / Pressed / Disabled) × isActive, lowercase booleans. Active segments fill brand-blue, inactive carry the brand-blue outline; the label is edited on the instance.
Composable
Pass
Nests inside the Segmented Control row as an instance, so atom changes propagate to every segment across every item count.
Behavior
State iOS Android Figma Property Notes
Active Yes Yes Active=true Fills brand-blue #005CE5 with a white label. The currently-selected segment.
Inactive Yes Yes Active=false White fill with a 1.5px brand-blue outline and a brand-blue label.
Pressed Yes Yes State=Pressed Touch-down feedback — active darkens to #2340A9; inactive shows a light #F6F9FD (72%) fill under the outline.
Disabled Yes Yes State=Disabled Muted #9BC5FD for both active fill and inactive outline; tap blocked.
Issues
  • v1.0: Active boolean moved to lowercase true/false (from Yes/No), matching the DS boolean standard. (C2)
  • v1.0: State=Pressed, Active=true variant added — the matrix is now the complete 3 × 2 = 6, so an already-active segment has a pressed treatment too (#2340A9). (C5)
  • v1.0: Segment label node renamed from #value to label. (C2)
  • Label is a text layer, not a text property: withdrawn — the label is set by overriding the text on the instance, which Figma allows without a property; owner decision, 6 Oct 2026, node 26628:50752 (C4)
Recommendations
  • Normalize the Active boolean to true/false. v1.0: Applied — lowercase, matching the DS standard. Rename
  • Complete the State × Active matrix. v1.0: Applied — added the missing pressed-active variant; the atom now ships all six combinations. State

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

The segment is composed by the parent Segmented Control; neither property is set directly. isActive forwards from the row’s selected index and State derives at runtime, so both are outputs of the row rather than inputs to the segment.

Figma PropertySwiftUICompose
State — Default, Pressed, Disabled derived — isPressed from the gesture, isEnabled from the environment derived — interactionSource for pressed, enabled for disabled
isActive — true, false isSelected: Bool — forwarded from the row’s selectedIndex selected: Boolean — forwarded from the row’s selectedIndex
label — a TEXT layer, not a confirmed text property Segment.label: String Segment.label: String
SwiftUI
ios/Components/Toggle/EBSegment.swift
Jetpack Compose
android/components/toggle/EBSegment.kt
Usage Snippets Planned API
Composed by the row
// The segment is never constructed directly. The row makes one per
// entry; isActive is index == selectedIndex.
EBSegmentedControl(
    segments: ["Label", "Label", "Label"],
    selectedIndex: $selected
)
// The segment is never constructed directly. The row makes one per
// entry; isActive is index == selectedIndex.
EBSegmentedControl(
    segments = listOf("Label", "Label", "Label"),
    selectedIndex = selected
)
Disabled and pressed
// State=Disabled comes from the row, not per segment —
// every segment mutes together.
EBSegmentedControl(segments: segments, selectedIndex: $selected)
    .disabled(true)

// State=Pressed is the touch-down frame. Nothing to set:
// the active segment darkens to #2340A9, an inactive one takes
// a #F6F9FD 72% wash under its label.
// State=Disabled comes from the row, not per segment —
// every segment mutes together.
EBSegmentedControl(
    segments = segments,
    selectedIndex = selected,
    enabled = false
)

// State=Pressed is the touch-down frame, read from
// interactionSource.collectIsPressedAsState().
Accessibility
RequirementiOSAndroid
Role Each segment is a .button with .isSelected on the active one. Apply Role.RadioButton per segment inside the row’s Modifier.selectableGroup().
Selection announcement Announce position in the set — "Label, selected, 1 of 3" — so the user knows how many segments there are. Set stateDescription and let selectableGroup() supply the index.
Minimum target The segment is 40.33pt tall — under the 44 × 44pt minimum. Extend with .contentShape rather than resizing; the drawn height belongs to the row. Under the 48 × 48dp minimum. Use Modifier.minimumInteractiveComponentSize(), which pads the target without changing the drawn height.
Disabled Not focusable; .disabled(true) comes from the parent control, so the whole row leaves the focus order together. enabled = false on the row; not focusable.
Colour is not the only cue Active versus inactive is carried by fill and label colour alone — no icon, no weight change. Pair the control with a heading that names what is being switched. Same. The right-edge divider is decorative and must not be announced.
Dynamic Type / font scaling The label is 16pt and must scale; the 40.33pt segment has to grow with it rather than clip. Use sp for the label and let the segment height follow fontScale.
Usage Guidelines

Do

Let the parent <code>Segmented Control</code> own selection. <code>isActive</code> is <code>index == selectedIndex</code>, and <code>State</code> derives from the gesture and the enabled flag.

Don't

Don’t place a segment on its own or drive <code>isActive</code> per instance — two active segments in one row is a state the component cannot express.

Do

Treat the right-edge line as a divider between segments. It only renders on inactive segments, which is why it earns its place in a 3- or 4-segment control.

Don't

Don’t read it as a border. The stroke is declared on all four sides but only the right one has weight, so an inactive segment is not outlined — it is separated.

Do

Keep labels short enough to fit at the row’s widest segment. The segment is 16px padding either side of the label and every segment shares the row’s width equally.

Don't

Don’t rely on truncation. The label has no wrap or ellipsis treatment in any of the six variants, so an over-long label pushes the row past its container.

Do

Disable the whole row when the choice is unavailable — <code>State=Disabled</code> mutes active and inactive alike, to <code>#9BC5FD</code>.

Don't

Don’t disable a single segment to show it is unavailable. There is no per-segment disabled path, and a muted segment beside live ones reads as selected-but-faded.

Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Ready Each variant is a frame with a single label text layer. The inactive outline is a right-side stroke — 1px right, 0 on the other sides — which is how Figma stores a one-sided border.
C2 Variant & Property Naming Ready State is PascalCase with Title Case values; isActive takes the is prefix with lowercase true/false. The property was Active in the retired 2026 Working File node and now matches the C2 boolean convention.
C3 Token Coverage Ready Every value is token-bound: fills bg/color-bg-primary (+ hover, disabled) and bg/color-bg-overlay-light-strong, strokes border/color-border-primary (+ disabled), label colours text/color-text-*, padding space/space-12 / space-16, text style Primary/Label/Base.
C4 Native Mappability Ready Maps cleanly: isActive forwards from the row’s selected index, State derives from the press gesture and the enabled flag, and the label is a plain string set on the instance.
C5 Interaction State Coverage Ready Complete 3 × 2 matrix with no gaps — Default, Pressed and Disabled at both isActive values, including the pressed-active frame that the 1.0.0 assessment added.
C6 Asset & Icon Quality Not Applicable Text-only segment. No icons, no images, nothing to vectorise.
Variants Inventory (6 total)

State (3) × isActive (2) = 6. Complete matrix, no gaps. Active segments are a solid fill with a white label; inactive ones have no fill — bar Pressed — and carry a right-edge divider in the state’s own colour.

StateisActiveNode IDBackgroundDividerLabel
Defaulttrue26628:50753#005CE5–#FFFFFF
Pressedtrue26628:50755#2340A9–#FFFFFF
Disabledtrue26628:50757#9BC5FD–#FFFFFF
Defaultfalse26628:50759–#005CE5#005CE5
Pressedfalse26628:50761#F6F9FD @ 72%#005CE5#2340A9
Disabledfalse26628:50763–#9BC5FD#9BC5FD
2.0.1 — October 2026Patch
Component Review · node 26628:50752
Label is a text layer, not a text property. Filed here, where the label lives; it was carried as an inherited issue on Segmented Control - Group.
C4 Open
Consistent → partial, verdict Keep → Fix. The state matrix is complete; the label is the one gap.
Docs
Label issue withdrawn; verdict back to Keep. The label is set by overriding the text on the instance, which Figma allows without a property — owner decision.
C4 Withdrawn
C1 and C3 → ready; native status Needs Refinement → Ready. Read over the Figma REST API on 26628:50752: every fill, stroke, padding and the text style is token-bound, and the inactive outline is a standard right-side stroke.
C1 C3 Resolved
2.0.0 — September 2026Major
Repointed to the Sticker Sheets file · node 26628:50752
The record assessed a different Figma file. meta.node pointed at 4111:10773 in the 2026 Working File (pbxY8a2xcIfVZKxwnud9Xe); the live component is 26628:50752 in Sticker Sheets v2 (HwWDwPit2xJjDH4zszOZ5o). Every value in this entry is re-read from the latter. Dated when found — nothing records when the move happened.
Rebuild
Inactive segments have a right-edge divider, not an outline. Every tab described "a 1.5px brand-blue outline" around the whole segment. export_node_as_image shows a single vertical line on the right edge only — the stroke is declared on all four sides and just the right one has weight. The component is a segment divider, not a bordered button.
C1 Resolved
Active renamed isActive. The property now takes the is prefix with lowercase true/false, matching the C2 boolean convention and its siblings in the Radio family.
C2 Resolved
Two label colours were undocumented. The Colors section listed active and inactive-default labels only. Pressed-inactive is #2340A9 and disabled-inactive is #9BC5FD — both now recorded, so all six variants have a label value.
C3 Resolved
The pressed-inactive wash carries 72% opacity. Documented as a flat #F6F9FD; the fill is #F6F9FD at 0.72.
C3 Resolved
Typography resolved to a style name. Primary/Label/Base, matched. Replaces the font spec Proxima Soft Bold · 16 / 16 · +0.25, which the guide bans as a stand-in for a style name.
C3 Resolved
The Style tab became interactive. The component had no demo script at all — demoControls: [] and a hand-written inline-styled span for a preview. It now has public/scripts/demos/segmented-control-button.js with State and isActive controls, and the preview and the server-rendered markup come from one renderer.
Docs
Variants Inventory repointed. Six rows on 4111:* / 4715:* nodes from the retired file, replaced with 26628:50753–26628:50763, each carrying its verified background, divider and label.
Docs
Code tab filled in. Installation had no blocks, and Usage Snippets and Usage Guidelines were both empty. Property Mapping used Prop=Value form and is now one prose row per property.
Docs
Scorecard note corrected. C1 described layers as container / label; each variant has a single label TEXT child and no container frame. C2 said Active was "retained by design" — it was renamed.
Docs
The stroke is declared on four sides with weight on one. That is what made the outline reading look right for two versions. Until the three zero-weight sides are turned off, anyone reading the node sees a fully bordered button.
C1 Open
Divider weight is unread. The Talk To Figma plugin does not return strokeWeight, and the outside-aligned stroke falls outside the SVG export bounds, so get_svg drops it entirely. The preview draws 1px — the one value in the Style tab that is not attested.
C3 Open
The segment is under the minimum touch target. 40.33pt tall against 44 × 44pt on iOS and 48 × 48dp on Android. The Accessibility table says to extend the hit area rather than resize, since the drawn height belongs to the row.
C5 Open
Whether label is an exposed text property is unverified. The layer is named label, not #label, and the property panel was not supplied. If it is not exposed, segment text cannot be set in Figma at all — a gap rather than a documentation issue.
C2 Open
Token bindings unread, so all twelve colour rows carry —. No segmented-control/* namespace is registered; the previous record’s paths were not carried forward because they named the retired file.
C3 Open
The Overview tab still describes the retired node. meta.node, meta.figmaUrl, the description, the verdict and the Behavior table all name 4111:10773, Active, and the 1.5px outline. Overview scope — needs a Component Review run before the page stops contradicting itself.
Docs
1.0.0 — July 2026Major
Initial Assessment · node 4111:10773
Component assessed — the single-segment atom for the Segmented Control family. 6 variants across State × Active. Documented
Initial
Active boolean lowercased — Yes/No → true/false, matching the DS standard. Resolved
C2 Resolved
Pressed-active variant added — completes the 3 × 2 matrix; an already-active segment now has a pressed treatment (#2340A9). Resolved
C5 Resolved
Label node renamed — #value → label. Resolved
C2 Resolved
Code Connect — unblocked; not yet registered. Open
C7 Open