A flat segmented progress indicator — a row of rounded dashes where filled dashes mark current and earlier steps.
Restructure — collapse the boolean slot flags and ordinal enum into <code>current: Int</code> + <code>total: Int</code>
Visually the component is already in great shape — vector dashes, token-bound fills, clean rounded geometry. The structural problem is the schema: highlighted is an ordinal enum (1st…10th) when it should be a number, and the 10 propNStepper booleans emulate what should be a single total scalar. Rebuild as one variant with current: 1…10 and total: 2…10, and fix the duplicate 6th layer name that covers positions 7–10. Native side stays a custom component (no platform primitive renders "N equal-width dashes" out of the box).
In Context
Stepper - Dash sits above multi-step flows to tell the user where they are — KYC wizards, onboarding carousels, checkout funnels. The dashed format reads as "position in a sequence" rather than "percent done".
Live Preview
Properties (proposed)
total
current
Figma (today)
highlighted2nd
visible slotsprop1–prop4 = true
DS Health
Reusable
Partial
Applies to any 2–10 step linear flow. Hard-capped at 10 (the ordinal enum only goes up to 10th), so longer flows need a different component.
Self-contained
Pass
Pure vector — rounded rectangles bound to main/stepper/color/bg and main/stepper/color/bg-track. No raster assets, no external dependencies.
Consistent
Warn
Ordinal enum (1st…10th) where a number belongs; 10 boolean visibility flags where a scalar total belongs; layer names duplicate 6th across positions 7–10.
Composable
Partial
Frame is 268 px wide with fill-container dashes, so it stretches inside a parent. Family-wise, Dash, Bullet, and Circular each ship independently; a unified EBStepper(style:) API would compose better.
Behavior
State
iOS
Android
Figma Property
Notes
Default (current)
Yes
Yes
highlighted=1st…10th
Dashes 1…current fill #005CE5; dashes current+1…total fill #D2E5FF.
Completed
N/A
N/A
Not modeled
No distinct "all done" state. Once current === total the final dash just reuses the same brand blue — consider a positive-green variant when the whole flow finishes successfully.
Error
N/A
N/A
Not modeled
Flows that can fail mid-way (KYC rejected, upload failed) have no way to indicate which step errored. Add a per-dash status or a component-level error state.
Tappable step
N/A
N/A
Not modeled
Display-only today — no pressed or focused variant. If designers want to allow tapping a completed dash to return, that interaction needs spec'ing.
Open Issues
Total step count encoded as 10 boolean props instead of an integer. Ships prop1Stepper…prop10Steppers — ten toggles used as visibility flags for "how many dashes to render". To spec a 4-dash stepper a consumer flips four toggles on and six off; to change to 5 dashes they flip one more. Should be a single total: Int (or total: 2 | 3 | … | 10 enum). C2 · Variant & Property Naming
highlighted is ordinal, not numeric.highlighted = 1st | 2nd | … | 10th reads as a label, not a position. Native APIs and product code want an integer they can feed a current / total calculation — rename to current and switch to a numeric range. C2 · Variant & Property Naming
Duplicate 6th layer name across positions 7–10. In the highlighted=1st variant, dash layers are labelled 1st, 2nd, 3rd, 4th, 5th, 6th, 6th, 6th, 6th, 6th. Looks like a copy-paste oversight when the component was extended from 6 to 10 slots. Rename to 7th…10th. C1 · Layer Structure & Naming
No native primitive matches. Neither SwiftUI nor Material 3 ships a "segmented dash" progress indicator. Implementation is a custom HStack/Row of rounded rectangles — manageable, but the DS must own the composable and its theming. C4 · Native Mappability
No completed, error, or interactive state modeled. Only the default two-tone (brand / track) rendering exists. Add variants for completed-successfully (green), error-at-step-N (red), and optionally pressed/focused if steps are tappable. C5 · Interaction State Coverage
Code Connect mappings not registered. Blocked until the ordinal enum and boolean visibility props collapse into current and total. Mapping today's schema would codify the anti-pattern. C7 · Code Connect Linkability
Design Recommendations
Replace the 10 propNStepper booleans with a single total property. Today a designer builds "4-dash stepper" by toggling prop1Stepper=true, prop2=true, prop3=true, prop4=true, prop5..10=false. Replace with total: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10. Variant math drops from (10 ordinal × 2^10 boolean combos, nominally) to 9 × 10 = 90, and more importantly the schema becomes legible — "4 dashes, step 2 active" instead of "highlighted=2nd + 4 booleans on, 6 booleans off". Property
Rename highlighted = 1st…10th to current: 1…10. The property name reads as a Boolean ("is highlighted?") and the values read as rank labels. Both are wrong — the value is a numeric index. Rename to current and switch values from ordinal strings to integers so consumers can do current / total math. Rename
Fix duplicated 6th layer names on slots 7–10. Rename the last four inner dash layers to 7th, 8th, 9th, 10th so the inspector and dev handoff read cleanly. Low-effort cleanup. Rename
Add status states for completed, error, and loading. Introduce an optional component-level state: default | success | error that repaints the whole row (green when the flow finishes, red when the current step errors). Indeterminate / loading is optional — a subtle pulsing animation on the current dash while an async step resolves. Tokens: main/stepper/color/bg-positive, main/stepper/color/bg-negative. State
Unify Stepper - Dash, Stepper - Bullet, and Stepper - Circular into one EBStepper API. All three render the same underlying data (current, total) and differ only in the visual treatment of each slot. On the native side, ship one EBStepper(current:total:style:) with style: .dash | .bullet | .circular instead of three separate components — the state, accessibility, and layout logic are identical across styles. Figma can keep three sibling records for the sticker sheet, but the API is one surface. Family
Document a canonical "dash height × total" sizing chart. The single frame is 268 px wide with 4-px dash height and 4-px gap. As total grows the individual dash width shrinks — at total=10 each dash is ~22 px. Document the minimum sensible per-dash width and the component's fill behavior inside narrower parents. Docs
Announce "Step X of Y" to screen readers. Dashes are purely decorative — assistive tech sees nothing today. Wrap in a container with accessibilityLabel = "Step \(current) of \(total)" on iOS and Modifier.semantics { contentDescription = … } on Android. A11y
Spec a vertical orientation for long flows. With 10 dashes in a narrow column, individual dashes get uncomfortably short. Add orientation: horizontal | vertical so flows can stack top-to-bottom when horizontal width is constrained. Property
Styles
Stepper - Dash
DESDEV
Horizontal row of equal-width rounded dashes. Dashes 1…<code>current</code> render in brand blue (<code>bg</code>); dashes <code>current+1</code>…<code>total</code> render in track blue (<code>bg-track</code>). All fills are real vector rectangles bound to stepper tokens.
Dash-style step indicator. Filled dashes are brand-blue; empty dashes are pale-blue.
Role
Token
Default
Filled dash
stepper/color/bg
#005CE5
Empty dash
stepper/color/bg-track
#D2E5FF
Property Mapping
Figma Property
SwiftUI
Compose
highlighted: 1st | … | 10th
current: Int (1…10)
current: Int
prop1Stepper…prop10Steppers (Bool × 10)
total: Int (2…10)
total: Int
(not modeled)
status: default | success | error
status: EBStepperStatus
(not modeled)
orientation: horizontal | vertical
axis: Axis
bg token
unchanged
EBColors.stepperBg
bg-track token
unchanged
EBColors.stepperTrack
Accessibility
Requirement
iOS
Android
Progress role
Expose as a single .accessibilityElement() with the .progressbar trait, value Double(current) / Double(total), label "Step 2 of 4".
Wrap in Modifier.semantics { role = Role.ProgressBar; progressBarRangeInfo = ProgressBarRangeInfo(current.toFloat(), 0f..total.toFloat()) }.
Label announcement
VoiceOver should read "Step 2 of 4, 50%". Localize the substitutions.
TalkBack same — use stateDescription for the "Step X of Y" phrasing.
Non-decorative colors
Brand blue on track blue is a 3.1:1 non-text contrast ratio — passes WCAG 1.4.11. OK.
Same ratio.
Grouping
Use .accessibilityElement(children: .ignore) so VoiceOver hears one unified announcement, not N dash descriptions.
Use Modifier.semantics(mergeDescendants = true).
Dynamic Type / text scale
Stepper has no text, so Dynamic Type doesn't apply to the component itself. Labels paired above/below should scale.
Same — font scale applies to paired labels, not dashes.
Criteria Scorecard
ID
Criterion
Status
Notes
C1
Layer Structure & Naming
Needs Refinement
Inner dash layers on slots 7–10 are all named 6th (copy-paste bug). Otherwise clean.
C2
Variant & Property Naming
Requires Rework
highlighted = 1st…10th should be numeric current; 10 propNStepper booleans should collapse to total: Int.
C3
Token Coverage
Ready
All fills bound to main/stepper/color/bg and main/stepper/color/bg-track. Gap uses space/space-4. Add bg-positive / bg-negative when status states land.
C4
Native Mappability
Requires Rework
No native primitive. Custom HStack/Row of rounded rectangles. Small composable, but must be owned by the DS.
C5
Interaction State Coverage
Requires Rework
Only default two-tone. Missing completed/success, error, and (optional) tappable-step pressed/focused.
C6
Asset & Icon Quality
Ready
Pure vector rectangles — no raster assets. Resolution-independent, theme-able. Best-in-class for the Stepper family.
C7
Code Connect Linkability
Not Mapped
Blocked until the ordinal enum and boolean slot flags collapse into current + total.
Variants Inventory (10 total)
A single highlighted axis with 10 ordinal values (1st, 2nd, …, 10th) = 10 variants. Each variant additionally exposes 10 boolean propNStepper visibility flags (not counted in the variant total — they're instance props). The proposed architecture replaces all of them with current: Int + total: Int, producing 9 × 10 = 90 representable states with no boolean juggling.
Verdict: Restructure — Collapse highlighted = 1st…10th + 10 propNStepper booleans into current: Int + total: Int. Asset quality already clean (pure vector). Open
Schema
C1 — Layer naming — Dash layers at positions 7–10 are all labelled 6th (copy-paste bug). Rename to 7th…10th. Open
C1
C2 — Ordinal enum + boolean flags — highlighted is ordinal (1st…10th) where a number belongs; 10 propNStepper booleans emulate what should be total: Int. Rename and collapse. Open
C2
C4 — Native mapping — No platform primitive matches. Requires custom EBStepperDash on both platforms. Consider unified EBStepper(style:) shared with Bullet and Circular. Open
C4
C5 — Missing states — No completed-success, error, or tappable-step variants. Add status: default | success | error and optional pressed/focused. Open
C5
C6 — Asset quality — Pure vector rectangles bound to main/stepper/color/* tokens. No raster. Ready. Ready
C6
C7 — Code Connect — Blocked until the schema collapses to current + total. Open