A horizontal slider control with a continuous fill, a 16×16 knob, and a small percentage tooltip above the knob.
EBSlider primitive that takes value: Double, range: ClosedRange, and step: Double?; the fill width is computed at render time. Also missing: Pressed (knob drag), Focused (keyboard), and Disabled state coverage.Used in volume / brightness pickers, transfer-amount approximations (e.g. "use X% of your wallet"), and progress-style settings (e.g. "Round-up to nearest …%"). On phone widths the entire control sits in a 360–366 px row.
value=0% through value=100%) but the format is decorative; the actual value is the meaningful part.| State | iOS | Android | Figma Property | Notes |
|---|---|---|---|---|
| Drag knob | N/A | N/A | Not modeled | No Pressed treatment on the knob during drag. Common pattern: knob enlarges to 24 × 24 with a soft halo while held. |
| Tap track | N/A | N/A | Not modeled | Tapping a position on the track should jump the knob there with a short animation. Not spec'd. |
| Keyboard / focus | N/A | N/A | Not modeled | ←/→ should nudge by step, Shift+←/→ by 10×step. No visible focus ring spec. |
| Disabled | N/A | N/A | Not modeled | No disabled appearance. Track and knob should both dim; pointer-events disabled. |
| Tooltip visibility | N/A | N/A | Always-on | Tooltip is always visible across all 11 variants. Typical mobile pattern is to show only while dragging, with a fade-in/out. |
| Value live region | N/A | N/A | Not modeled | For accessibility, the value should announce on change ("50 percent") via a polite live region, debounced. |
- 11 variants for a continuous value. The component models position as a 10%-stepped enum (
value=0%…value=100%). This isn't how sliders work — they're continuous. Replace with a singleEBSlider(value: Double, in: range, step: step?)primitive that computes fill width fromvalueat render time. C1 · Layer Structure & Naming - Tooltip text is baked, not a slot. The tooltip always shows the percentage value formatted as
NN%. Real sliders often display currency ("$1,200"), distance ("3.5 km"), or a custom label. Expose the tooltip's content as atooltipFormatter: (Double) -> Stringclosure or a Slot. C4 · Native Mappability - Tooltip is always-on with no visibility prop. iOS Apple sliders show the value on drag only, fading out a second after release. Material 3 sliders show the value bubble similarly. Always-on is fine for some use cases but should be a prop (
tooltip: .always | .onDrag | .never) not baked. C5 · Interaction State Coverage - No Pressed (drag) state. Knob doesn't enlarge or show a halo on touch-down/drag. Without that feedback, the knob feels stuck — users can't tell whether the drag is active. C5 · Interaction State Coverage
- No Disabled / Focused state coverage. Only one state shipped per value. Disabled (dim track + non-interactive knob) and Focused (keyboard ring) are mandatory for forms and web embeds. C5 · Interaction State Coverage
- No range / step / min / max axes. Real sliders need
range: 0...100,step: 1(orstep: nilfor continuous),min/max. None of these exist today — every consumer would re-invent. C4 · Native Mappability - Code Connect mappings not registered. Blocked on the restructure — once
valueis a continuous prop, a single Code Connect entry replaces 11 variant entries. C7 · Code Connect Linkability
- Replace 11 variants with a single continuous slider primitive. Target API:
EBSlider(value: Binding. Fill width computed as, in: 0...100, step: nil, tooltip: .onDrag) value / (max - min) * trackWidth; knob position follows. Drops Figma variant count from 11 to 1 and gives native devs a single 1:1 mapping. Family - Make the tooltip content a closure / slot.
tooltipFormatter: (Double) -> Stringwith a default of{ "\(Int($0))%" }. Lets consumers show currency, distance, or any label without detaching. Slot - Add tooltip-visibility axis.
tooltip: .always | .onDrag | .never. Default.onDragmatches platform expectations on both iOS and Android. Property - Spec Pressed / Focused / Disabled. Pressed: knob 16 → 24 with halo, scale animates over 120 ms ease-out. Focused: 2 px brand-blue ring offset 2 px around the knob. Disabled: 40% opacity on track + fill + knob, pointer-events disabled, no tooltip. State
- Add range / step / min / max props. Every concrete slider in the wild needs these. Make them first-class on the API so consumers don't reinvent them. Property
- Document the A11y model. Role:
slider.aria-valuemin/aria-valuemax/aria-valuenowwired to range + value. Keyboard: ←/→ step, Shift+←/→ 10×step, Home/End to ends. VoiceOver/TalkBack: announce "50 percent" on value change, debounced 250 ms. A11y
366 × 28 row with a 10-tall pill track. Fill width grows with the value; the 16 × 16 white knob sits at the fill's right edge with a small percentage tooltip floating 4 px above.
All colors stay consistent across the 11 variants; only the fill width changes.
| Role | Token | Default |
|---|---|---|
| Track bg | slider/color/track/bg | #D2E5FF |
| Fill | slider/color/fill | #005CE5 |
| Knob bg | slider/color/knob/bg | #FFFFFF |
| Knob border | slider/color/knob/border | #E5EBF4 |
| Tooltip bg | slider/color/tooltip/bg | #005CE5 |
| Tooltip text | slider/color/tooltip/text | #FFFFFF |
Today's 11-variant matrix collapses to a single continuous prop once restructured.
| Figma Property | SwiftUI | Compose |
|---|---|---|
| value=0% … 100% | value: Binding | value: Float |
| (implicit 0–100 range) | in: ClosedRange | valueRange: ClosedFloatingPointRange |
| (no step today) | step: Double? | steps: Int? |
| (no tooltip prop today) | tooltip: SliderTooltipVisibility | tooltip: SliderTooltip |
| (no tooltip slot today) | tooltipFormatter: (Double) -> String | tooltipFormatter: (Float) -> String |
| (no callback today) | onChange: (Double) -> Void | onValueChange: (Float) -> Unit |
| Requirement | iOS | Android |
|---|---|---|
| Role | Conforms to Slider-style accessibility trait. .accessibilityValue("\(Int(value)) percent"). | Apply Role.Slider with progressBarRangeInfo for current / min / max. |
| Live announce | Use UIAccessibility.post(.announcement, "\(Int(value)) percent") debounced ~250 ms on drag. | Use announceForAccessibility debounced ~250 ms. |
| Keyboard / DPAD | ←/→ step by step (or 1 if nil); Shift+←/→ by 10×step; Home/End jump to bounds. | DPAD ←/→ step; long-press for 10×step. |
| Focus ring | 2 px brand-blue ring offset 2 px around the knob when focused (mac / iPad keyboard). | Modifier.focusable() with a visible 2 px outline. |
| Touch target | Knob hit area extended to 44 × 44 via .contentShape(Circle()). | Modifier.minimumInteractiveComponentSize() on the knob. |
| ID | Criterion | Status | Notes |
|---|---|---|---|
| C1 | Layer Structure & Naming | Requires Rework | 11 discrete variants for a continuous value. Replace with one primitive + value: Double. |
| C2 | Variant & Property Naming | Needs Refinement | value=NN% mixes display format into the prop value. Make value numeric and let the tooltip formatter handle display. |
| C3 | Token Coverage | Needs Refinement | Colors consistent but no slider/* token namespace registered. |
| C4 | Native Mappability | Requires Rework | Maps cleanly to Slider / Slider() after the value-as-prop restructure. |
| C5 | Interaction State Coverage | Requires Rework | Only one default state. No Pressed (drag), Focused, or Disabled coverage. |
| C6 | Asset & Icon Quality | Not Applicable | No assets used. |
| C7 | Code Connect Linkability | Not Mapped | Blocked on the value-as-prop restructure. |
11 variants on a single value axis at 10% increments. Same visual treatment across the matrix — only the fill width and tooltip percentage differ.
| # | value | Fill width | Size | Node |
|---|---|---|---|---|
| 1 | 0% | 7 | 366 × 28 | 3235:60723 |
| 2 | 10% | 37 | 366 × 28 | 3235:60734 |
| 3 | 20% | 74 | 366 × 28 | 3235:60745 |
| 4 | 30% | 110 | 366 × 28 | 3235:60756 |
| 5 | 40% | 147 | 366 × 28 | 3235:60767 |
| 6 | 50% | 183 | 366 × 28 | 3235:60778 |
| 7 | 60% | 220 | 366 × 28 | 3235:60789 |
| 8 | 70% | 256 | 366 × 28 | 3235:60800 |
| 9 | 80% | 293 | 366 × 28 | 3235:60811 |
| 10 | 90% | 329 | 366 × 28 | 3235:60822 |
| 11 | 100% | 358 | 366 × 28 | 3235:60833 |
EBSlider primitive with value: Double + range + step. Add Pressed / Focused / Disabled state coverage. Open