RestructureRequires Rework
Stepper - Circular Component link

A row of numbered circles whose ring arcs indicate progress through a multi-step flow.

Restructure — collapse 9 sibling components into one <code>Stepper - Circular</code> with <code>steps</code> and <code>current</code> properties
Step count is a scalar — not a component axis. The current schema has 9 top-level components (2..10 steps) and inside each, 10 symbols of pre-rendered ring arcs. Every step count ships ~N PNGs. Rebuild as a single component: steps: Int (range 2–10) and current: Int (1..steps). Redraw the ring as a stroked SVG arc derived from current / steps so the fill scales with math, not image swaps. Native side maps to a custom EBStepperCircular(current:total:) rendered over an HStack / Row of Circle shapes with .trim(from:to:) or drawArc.
In Context

Stepper - Circular appears at the top of multi-step flows (onboarding, KYC, form wizards) to show position and total count. Consumers pair it with a screen title and step-specific content.

Live Preview
1
2
3
4
Content (proposed)
steps
current
value2 of 4
DS Health
Reusable
Partial
Visually fits any multi-step flow, but the 9-sibling split forces consumers to swap components when step count changes, not just a prop.
Self-contained
Warn
Each step circle's ring arc renders via raster <img>. ~10 PNGs per sibling × 9 siblings ≈ 50+ assets for what should be one SVG arc with two parameters.
Consistent
Fail
Step count modelled as 9 top-level components. Every other scalar in the DS (Badge counter, Progress Bar target) is a property, not a component family. Breaks the naming hierarchy — "Stepper - Circular" is not a component, it's nine.
Composable
Partial
Fixed 45-px circles at 20-px gaps fit most screen widths for 2–6 steps; at 7+ steps the total width exceeds a typical mobile canvas (410+ px). No responsive shrink or wrap behavior.
Behavior
State iOS Android Figma Property Notes
Completed step Yes Yes ring = full Step index < current. Full ring fill in brand blue, number in brand blue.
Current step Yes Yes ring = partial arc Step index = current. Arc fills current / steps of the ring; number in brand blue.
Upcoming step Yes Yes ring = track only Step index > current. Track-only ring in light blue; number still in brand blue.
Clickable / interactive N/A N/A Not modeled Some wizards let the user tap a completed step to go back. No pressed / focused state exists today; add if interactive behavior is desired.
Vertical orientation N/A N/A Not modeled Horizontal only. Vertical steppers (common on narrow screens or list layouts) would need a second orientation mode or a sibling.
Open Issues
  • Step count is modeled as 9 sibling components instead of a steps prop. Today the family ships as Stepper - Circular - 2 Steps, … - 3 Steps, … … - 10 Steps — 9 top-level components that differ only by hardcoded count. Every other design system exposes one Stepper with steps: Int (or total: Int + current: Int). 9× maintenance, 9× Code Connect mapping, and no path to N=11+ without adding a 10th file. C1 · Layer Structure & Naming
  • Step ring arcs are pre-rendered raster <img> assets, one per step index. Each number=N symbol's ring is a baked PNG — the progressive fill is achieved by image swap, not by math. Blocks theming (can't retint), breaks at high DPI, and ships dozens of assets the native renderer doesn't need. Should be a stroked SVG arc with strokeDasharray (or Circle().trim(from:to:) on iOS, drawArc on Compose). C6 · Asset & Icon Quality
  • Variant axis number encodes position, not a property. The inner symbol uses number = 1 | 2 | … | N which both labels the circle (the displayed digit) and implicitly sets the ring-arc raster. Two concerns collapsed into one enum. Should be: index: Int (the number shown) and status: completed | current | upcoming (the visual). C2 · Variant & Property Naming
  • No native primitive is a 1:1 match — this needs a custom component. SwiftUI has no built-in circular stepper; Material 3 shows only a linear Stepper. Both platforms require a custom EBStepperCircular built from a Row/HStack of Circle/Box shapes with arc-drawn progress. Today's raster baking makes this worse — the dev can't reuse the asset. C4 · Native Mappability
  • No pressed / focused / disabled state modeled. Wizards often allow tapping a completed step to return to it. With no interaction states in Figma, the developer has to invent hover / pressed treatment and the designer has no reference. C5 · Interaction State Coverage
  • Code Connect mappings not registered. Blocked until the family collapses to one component and the raster arcs are replaced with vector strokes. Mapping 9 separate siblings would codify the anti-pattern into the tooling. C7 · Code Connect Linkability
Design Recommendations
  • Collapse all 9 siblings into one Stepper - Circular with steps and current properties. Delete Stepper - Circular - 2 Steps through … - 10 Steps as separate components. Create one Stepper - Circular with steps: 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 (enum) and current: 1 | 2 | … | 10 — or, if Figma supports integer ranges, a numeric property. Variant math drops from 9 top-level components × ~10 step symbols = ~90 variant assets to 1 component with a runtime-computed arc. Native API: EBStepperCircular(current: Int, total: Int). Family
  • Split the inner symbol's number axis into index (digit shown) and status (ring treatment). Today one enum does both. Separate them: index: Int for the label text and status: completed | current | upcoming for the ring fill. The outer component then computes status per slot from current. Property
  • Replace raster ring arcs with stroked SVG / vector arcs. Each step circle's ring should be a 2-px stroked circle with stroke-dasharray (or two half-circles via trim). Colors bound to main/stepper/color/bg-track (unfilled) and main/stepper/color/bg (filled). Same visual output, theme-able, resolution-independent, no PNG assets. Asset
  • Add a completed visual state with a checkmark icon option. Many wizards replace the digit with a check once a step is done. Today there's no variant for this — everything shows a number. Add showCheckOnComplete: Bool (or model it into the status enum as completed-check). State
  • Add an orientation property for vertical layouts. Long flows (8+ steps) don't fit a phone-width row. Vertical stacks are common for checkout or document review wizards. Add orientation: horizontal | vertical and spec the connector line between circles for both axes. Property
  • Spec a connector line between circles. Today the 9 sibling frames space circles with a 20-px gap but no visible connector. Most steppers draw a line from the trailing edge of one circle to the leading edge of the next, tinted to match completed (brand) vs upcoming (track) states. Add this to the spec — it's the difference between "a row of circles" and "a stepper". Property
  • Rename layers step-containerRing (and the ring arc layer → Arc). "step-container" is a technical label; "Ring" is what a designer or developer searches for. Rename
  • Build as a custom native component, not a ProgressView wrapper. Neither SwiftUI ProgressView nor Material LinearProgressIndicator visually match this pattern. Ship a dedicated EBStepperCircular: iOS uses an HStack of ZStack { Circle().stroke(track); Circle().trim(from:0,to:progress).stroke(fill); Text(index) }; Android uses a Row of Box(Modifier.size(45.dp)) with Canvas drawing drawArc(sweepAngle = progress * 360f). Composition
  • Announce "Step X of Y" to screen readers. The component is decorative by default — assistive tech reads only the number. Wrap in a semantic container that announces "Step \(current) of \(total)" (SwiftUI .accessibilityLabel, Compose Modifier.semantics { contentDescription = … }). A11y
  • Document the canonical composition and retire the sibling names. Update the sticker sheet page to show one Stepper - Circular with property controls; add a migration note pointing Stepper - Circular - N Steps consumers to the new steps prop. Docs
Styles
Stepper - Circular
DES DEV

Row of N 45×45 numbered circles, each with a ring that indicates position through the flow. 9 hardcoded sibling frames today; target is one component with a <code>steps</code> prop.

1
2
3
4
Properties (proposed)
steps
current
Properties
Step counts 2–10
Variant Circular
Number style Primary/Headlines/Block
Colors
Active label #005CE5
Active arc #005CE5
Inactive arc #D2E5FF
Layout
Step circle size 45 × 45
Ring stroke width 2
Gap between circles 20
Typography
DS style Primary/Headlines/Block
Font Proxima Soft Bold
Size 18
Line height 23
Tracking 0.25
Stepper Circular — Colors

Circular ring progress indicator with a numeric step label inside.

Role Token Default
Active label stepper/color/label #005CE5
Active arc stepper/color/bg #005CE5
Inactive arc stepper/color/bg-track #D2E5FF
Property Mapping
Figma PropertySwiftUICompose
Stepper - Circular - N Steps (×9 siblings) Stepper - Circular (single component) EBStepperCircular
(implicit in sibling name) steps: 2…10 total: Int
(implicit in number variant) current: 1…steps current: Int
number = 1 | … | N (inner symbol) index: Int + status: completed | current | upcoming (derived internally from current)
(horizontal only) orientation: horizontal | vertical orientation: Axis = .horizontal
(raster arc) Stroked SVG / Canvas arc Circle().trim(from: 0, to: progress).stroke(...)
Accessibility
RequirementiOSAndroid
Progress role Wrap the row in .accessibilityElement(children: .ignore) and expose a single label. Set .accessibilityValue("Step \(current) of \(total)"). Treat the row as a single semantic node via Modifier.semantics(mergeDescendants = true) with contentDescription = "Step $current of $total".
Value announcement VoiceOver reads "Step 2 of 4". Avoid announcing each circle individually — it's noisy and position-less. TalkBack reads the merged label. Update stateDescription when current changes to trigger re-announcement.
Icon-only digits The digit itself is already semantic. Ensure contrast: #005CE5 on white = 5.3:1 ✓ Same. Digit color passes WCAG AA for non-text graphics.
Interactive steps If completed steps are tappable, wrap each in a Button with .accessibilityHint("Tap to return to step \(index)"). Pressed state inherits from the button. Use Modifier.clickable(onClickLabel = "Return to step $index"). Ripple indication appears natively.
Contrast Ring fill #005CE5 on track #D2E5FF = 3.1:1 — passes 3:1 for non-text graphics (WCAG 1.4.11). OK. Same ratio.
Criteria Scorecard
ID Criterion Status Notes
C1 Layer Structure & Naming Requires Rework Step count is modeled as 9 sibling components instead of a property. Collapse to a single Stepper - Circular. Also rename step-containerRing.
C2 Variant & Property Naming Requires Rework Inner number axis conflates digit label and ring-arc rendering. Split into index + status (and lift current / steps to the top-level component).
C3 Token Coverage Ready All colors bound to main/stepper/color/{bg, bg-track, label}. Typography bound to Primary/Headlines/Block. Gap bound to space/space-20.
C4 Native Mappability Requires Rework No native primitive matches. Requires a custom EBStepperCircular. Today's raster arcs block direct primitive composition.
C5 Interaction State Coverage Requires Rework No pressed / focused / disabled / tappable-completed state modeled. Missing vertical orientation and check-on-complete variants.
C6 Asset & Icon Quality Requires Rework Ring arcs are raster <img> — one PNG per step index per sibling component. Replace with stroked SVG / Canvas arcs.
C7 Code Connect Linkability Not Mapped Blocked until 9 siblings collapse into one component and raster arcs are replaced with vector strokes.
Variants Inventory (0 total)

Today: 9 sibling components, one per hardcoded step count. Each sibling contains N step symbols (number = 1 … N) with baked raster ring arcs. Sum of step symbols: 2+3+4+5+6+7+8+9+10 = 54 step symbols (+ 9 outer frames = ~63 nodes). Target: 1 component with steps: 2…10 and current: 1…steps as runtime properties; ring arcs drawn by math, not asset.

#Sibling componentNodeFrame widthStep symbols
1Stepper - Circular - 2 Steps27:480361502 (number = 1, 2)
2Stepper - Circular - 3 Steps27:480202153
3Stepper - Circular - 4 Steps27:479992804
4Stepper - Circular - 5 Steps27:479733455
5Stepper - Circular - 6 Steps27:479424106
6Stepper - Circular - 7 Steps27:479064757
7Stepper - Circular - 8 Steps27:478655408
8Stepper - Circular - 9 Steps27:478196059
9Stepper - Circular - 10 Steps27:4776867010 (number = 1…10)
1.0.0 — April 2026Major
Initial Assessment · canonical node 27:47768 (10 Steps) + 8 siblings
Verdict: Restructure — Collapse 9 sibling components (Stepper - Circular - 2…10 Steps) into one Stepper - Circular with steps: Int and current: Int properties. Replace raster ring arcs with stroked SVG / Canvas. Open
Schema
C1 — Family structure — 9 top-level components differ only by hardcoded step count. Collapse into a single component with a steps property. Rename step-container layer → Ring. Open
C1
C2 — Property shape — Inner number = 1…N conflates digit label and arc-fill rendering. Split into index (label) + status (ring treatment). Lift current and steps to the parent component. Open
C2
C4 — Native mapping — No native primitive matches. Requires custom EBStepperCircular on both platforms built over HStack/Row of stroked circles. Open
C4
C5 — Missing states — Add pressed / focused for interactive steps, vertical orientation, and check-on-complete option. Open
C5
C6 — Raster arcs — Each step's ring is a pre-baked PNG. Replace with stroked SVG circles bound to main/stepper/color/bg-track and main/stepper/color/bg. Open
C6
C7 — Code Connect — Mappings pending restructure. Mapping 9 separate siblings would codify the anti-pattern. Open
C7