RestructureRequires Rework
Slider Component link

A horizontal slider control with a continuous fill, a 16×16 knob, and a small percentage tooltip above the knob.

Collapse 11 discrete variants into one continuous slider primitive
The component set ships 11 variants in 10% increments (0%, 10%, 20%, …, 100%). That treats a continuous value as a discrete enum and forces consumers to either pick the nearest variant (lossy) or detach (defeats the system). Replace with one 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.
In Context

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.

Round-up 50%
Live Preview
Properties
Value
DS Health
Reusable
Fail
11 discrete variants for a continuous value. Any consumer who needs 73% has to either pick "70%" / "80%" (lossy) or detach. The component is fundamentally the wrong shape for the data it represents.
Self-contained
Pass
Track, fill, knob, and tooltip all live inside the component with no external instance dependencies. Cleanly self-contained.
Consistent
Partial
Geometry, colors, and tooltip treatment are consistent across the 11 variants. Naming is consistent (value=0% through value=100%) but the format is decorative; the actual value is the meaningful part.
Composable
Warn
Tooltip text is baked. Real-world sliders often show a different label (e.g. "$1,200" instead of "50%") or hide the tooltip entirely. No slot for the tooltip content.
Behavior
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.
Open Issues
  • 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 single EBSlider(value: Double, in: range, step: step?) primitive that computes fill width from value at 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 a tooltipFormatter: (Double) -> String closure 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 (or step: nil for 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 value is a continuous prop, a single Code Connect entry replaces 11 variant entries. C7 · Code Connect Linkability
Design Recommendations
  • Replace 11 variants with a single continuous slider primitive. Target API: EBSlider(value: Binding, in: 0...100, step: nil, tooltip: .onDrag). Fill width computed as 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) -> String with 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 .onDrag matches 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-valuenow wired to range + value. Keyboard: ←/→ step, Shift+←/→ 10×step, Home/End to ends. VoiceOver/TalkBack: announce "50 percent" on value change, debounced 250 ms. A11y
Types
Default
DES DEV

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.

Properties
Value
Properties
Value 50
Colors
Track bg #D2E5FF
Fill #005CE5
Knob bg #FFFFFF
Knob border #E5EBF4
Tooltip bg #005CE5
Tooltip text #FFFFFF
Layout
Control 366 × 28
Track 366 × 10 · radius 99
Knob 16 × 16 · circle
Knob border 1 px solid
Tooltip 28 × 22 · radius 4 · arrow 28 × 4
Tooltip → track 4 (gap between arrow tip and track top)
Typography
Tooltip percentage Proxima Soft Bold · 12 / 12 · +0.25
Colors

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
Property Mapping

Today's 11-variant matrix collapses to a single continuous prop once restructured.

Figma PropertySwiftUICompose
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
SwiftUI
ios/Components/Slider/EBSlider.swift
Jetpack Compose
android/components/slider/EBSlider.kt
Accessibility
RequirementiOSAndroid
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.
Criteria Scorecard
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.
Variants Inventory (11 total)

11 variants on a single value axis at 10% increments. Same visual treatment across the matrix — only the fill width and tooltip percentage differ.

#valueFill widthSizeNode
10%7366 × 283235:60723
210%37366 × 283235:60734
320%74366 × 283235:60745
430%110366 × 283235:60756
540%147366 × 283235:60767
650%183366 × 283235:60778
760%220366 × 283235:60789
870%256366 × 283235:60800
980%293366 × 283235:60811
1090%329366 × 283235:60822
11100%358366 × 283235:60833
1.0.0 — 2026-05-19Major
Initial Assessment · node 3235:60722
Component assessed — 11 variants at 10% increments. Used in settings / amount-approximation flows. Documented
Initial
Verdict: Restructure — Promote to a single EBSlider primitive with value: Double + range + step. Add Pressed / Focused / Disabled state coverage. Open
Family
C1 — 11 discrete variants — Continuous value modeled as a 10%-stepped enum. Replace with a continuous primitive. Open
C1
C4 — Tooltip is baked — Always-visible, percentage-only. No way to swap to currency / custom format. Open
C4
C5 — Missing states — No Pressed (drag), Focused, or Disabled treatment. Tooltip-visibility axis also missing. Open
C5
C7 — Code Connect — Not registered. Blocked on restructure. Open
C7