Slider — spec
Citing Ant Design Slider, MUI Slider, shadcn-ui slider
Purpose
A control for selecting a numeric value (or a range) by dragging. Used for: volume, price filter, opacity, brightness, age range.
| Use Slider |
Use Input (numeric) |
| Approximate value matters more than exact |
Exact value matters |
| Continuous range with no obvious snap points |
Discrete options or precise numbers |
| Visual feedback as the user adjusts |
User knows the value (e.g., DOB year, transaction amount) |
For amounts (currency, distance), prefer Input. For preferences (volume, contrast adjustment), Slider.
Anatomy
Single value:
0 ──────●─────────── 100
45
Range:
0 ─────●━━━━━━━●─── 100
20 80
| Slot |
Required |
Notes |
| Track |
yes |
Background line |
| Range fill |
yes |
Filled portion (single: 0→thumb, range: thumb1→thumb2) |
| Thumb(s) |
yes |
Draggable circle(s) |
| Tick marks |
optional |
Small lines at intervals |
| Step labels |
optional |
Numbers at tick positions |
| Value display |
optional |
Tooltip at thumb OR fixed display nearby |
API
<Slider
value={volume}
onValueChange={setVolume}
min={0}
max={100}
step={1}
label="볼륨"
showValue
/>
<Slider
value={priceRange}
onValueChange={setPriceRange}
min={0}
max={1000000}
step={10000}
range
label="가격대"
formatValue={(v) => `₩${v.toLocaleString()}`}
/>
| Prop |
Type |
Default |
Description |
value / defaultValue |
number \| [number, number] |
— |
Single or range. [number, number] triggers range mode |
onValueChange |
(value) => void |
— |
Fires while dragging (debounced) |
onValueCommit |
(value) => void |
— |
Fires only on drag-end (use for expensive updates) |
range |
boolean |
inferred from value type |
Force range mode |
min / max |
number |
0 / 100 |
|
step |
number |
1 |
Increment per arrow key / tick |
marks |
{ value: number, label?: string }[] |
— |
Custom marks |
disabled |
boolean |
false |
|
label |
string |
— |
Visible label (above slider) |
showValue |
boolean |
false |
Render value as tooltip on thumb |
formatValue |
(value) => string |
String(value) |
Custom format |
orientation |
"horizontal" \| "vertical" |
"horizontal" |
|
inverted |
boolean |
false |
Right-to-left direction |
States
| State |
Visual |
| Default |
Track + thumb at value position |
| Hover (thumb) |
Thumb slightly larger or shadow |
| Focus-visible |
2px ring around thumb |
| Dragging |
Cursor: grabbing; thumb scaled up; tooltip visible |
| Disabled |
Track + thumb muted, no events |
Sizes
| Size |
Track height |
Thumb size |
sm |
2px |
14px |
md (default) |
4px |
18px |
lg |
6px |
22px |
For mobile touch: md minimum. The thumb's hit area extends beyond the visible thumb by ~12px each side to support thumb dragging.
Tokens consumed
--color-bg-subtle (track)
--color-primary-default (range fill, thumb)
--color-bg-default (thumb center, ring)
--color-text-primary (value display)
--color-focus-ring
--space-xs, --space-sm
--radius-full (thumb)
--motion-fast, --easing-out
--shadow-card (thumb shadow)
Accessibility — WAI-ARIA Slider pattern
- Single thumb:
role="slider", aria-valuenow, aria-valuemin, aria-valuemax, aria-orientation, aria-label (or aria-labelledby).
- Range: two
role="slider" thumbs, each with their own valuenow + label ("Minimum price" / "Maximum price").
aria-valuetext if the displayed value isn't a plain number (e.g., "₩50,000" instead of "50000").
Keyboard
| Key |
Behavior |
Tab |
Reach slider (thumb 1; if range, then thumb 2) |
← / ↓ |
Decrement by step |
→ / ↑ |
Increment by step |
Home |
min value |
End |
max value |
PageUp / PageDown |
Larger step (typically 10× step) |
For range slider with overlapping thumbs: each thumb maintains independent focus + keyboard control. Thumb that would cross the other "pushes" it (or stops at the other's position).
Touch / drag behavior
- Hit area for thumb: 44×44 pt minimum (extend with invisible padding).
- Drag the track: should jump-to-value-and-start-drag from any track position.
- Snap to step: cleanly, no fractional positions.
- Vibration / haptic feedback (mobile): subtle haptic at each step crossing for some apps; off by default for sliders that are continuous-feeling.
Edge cases
- Range thumbs overlap: when min and max meet, thumbs occupy the same space. Allow this (they're interchangeable). Visual: render z-index so the active one is on top.
step doesn't divide max - min evenly: thumb snaps to nearest step; the max position might not be reachable. Prefer round-numbered max + step combinations.
- Very large ranges with small step: 0–1,000,000 with step=1 gives a million positions but only ~1000 px of track. Thumb position is still continuous; consider bumping step to 1000 or showing a numeric input next to the slider for precision.
- Vertical orientation: arrow keys reverse on
↑/↓ semantically (up = increase). Keep that.
- RTL: track flows right-to-left automatically with
dir="rtl". Use logical CSS.
- Dragging while page scrolls: prevent accidental page scroll on touch — use
touch-action: none on the slider.
Code example
// Volume control
<Slider
label="볼륨"
value={volume}
onValueChange={setVolume}
min={0}
max={100}
step={1}
showValue
formatValue={(v) => `${v}%`}
/>
// Price range filter
<Slider
label="가격대"
value={priceRange}
onValueChange={setPriceRange} // updates UI live
onValueCommit={fetchResults} // server-fetch only on release
min={0}
max={1000000}
step={10000}
range
marks={[
{ value: 0, label: "₩0" },
{ value: 250000, label: "₩25만" },
{ value: 500000, label: "₩50만" },
{ value: 750000, label: "₩75만" },
{ value: 1000000, label: "₩100만+" },
]}
formatValue={(v) => `₩${v.toLocaleString()}`}
/>
// Disabled state with explanation
<Slider value={50} disabled aria-label="요금제 한도 도달" />
Don't
- Don't use Slider for exact numeric input where the exact value matters. Use a number input (or both — common in price filters: slider + two number inputs synced).
- Don't omit
aria-label (or label prop). Sliders without an accessible name are unusable.
- Don't fire expensive operations on
onValueChange (debounce 100ms minimum, or use onValueCommit).
- Don't disable the slider without explaining why (tooltip or inline help).
- Don't have
step so small that arrow keys feel useless (1 over a 0–1,000,000 range — 1,000,000 keypresses).
- Don't show a slider for binary choices. Use a Switch.
- Don't layer two sliders on top of each other to fake range. Use proper range mode.
References
Cross-reference