Slider
Adjust a bounded numeric quantity with real endpoints, explicit units and alternatives to dragging.
Choose a bounded adjustment
Use Slider for a numeric value or range where approximate spatial adjustment is useful. Prefer an exact numeric input when precision or extensive entry dominates. Provide keyboard and single-pointer alternatives to dragging.
A progress indicator is read-only task status; a meter is a scalar measurement. A slider is an input. Its value does not establish confidence, language proficiency, review or permission.
Anatomy and dimensions
| Part | Current contract |
|---|---|
| Root | Div and shared values; class callback receives actual root state |
| Control / track | Horizontal control 44px high, full-width 6px visual track with readable boundary |
| Thumb | 44×44px target with 20px visual dot; nested native range input has the name/value |
| Range | One indexed thumb per actual array value, including SSR; endpoints remain separately named |
| Vertical | 44px-wide root/control, 12rem control height, 6px vertical track |
| Alignment | Wrapper default edge retains the whole 44px target within the control; primitive center can be explicitly selected |
| Touch / focus | Perpendicular page panning allowed; focus-within shows the token ring on the thumb target |
| Direction | DirectionProvider plus DOM dir supplies primitive interaction and text/layout direction |
Material slider variants, effect and visual feedback
The May 2025 reference names standard (formerly continuous), centered and range variants; discrete becomes the stops configuration. It adds horizontal/vertical orientation, five track sizes and optional inset icons on larger standard tracks. Some platform sizes exist only as tokens. These native presets are not props or dimensions of the installed web Slider.
Choose the domain first. A standard adjustment starts at its minimum; a centered visual starts from a meaningful neutral origin; a range exposes two separately named endpoints. Negative bounds alone do not turn this wrapper’s minimum-origin fill into a centered indicator. This example demonstrates the actual supported single/range API and documents that visual boundary rather than claiming a native centered/morph implementation.
| Reference decision | Local web contract and application choice |
|---|---|
| Immediate outcome | The local sample level immediately changes a real preview’s type size, and the range filters declared local records. Exact entry and slider input share the same state. A commit callback records an interaction boundary; it is not a save receipt. |
| Track and endpoints | Reference active/inactive tracks share size; inactive endpoints need 3:1 contrast through track, stop or other anchors. Local track boundary and thumb visual provide visible anchors. Sparse native stops, shape narrowing and active-track icon relocation are design options, not automatically installed by step or disabled props. |
| Native size reference | Native track heights are 16/24/40/56/96dp, with 44/44/52/68/108dp handles and 4dp handle width. Local 44px targets and 6px track remain the implemented geometry. Do not invent five web size props, native inset icons or an animated handle-width contract. |
| Value display and exact entry | If a value is shown next to the control, a popup is optional; range popup guidance shows one active value at a time. The local exact number field immediately follows the single slider in Tab order, synchronizes valid input both ways and exposes actual bounds. Supply understandable units rather than a bare percentage when the domain differs. |
| Orientation and direction | Horizontal values increase logically with DirectionProvider and DOM direction; vertical is a separately tested choice. The reference discourages vertical range sliders for cognitive clarity. The lab keeps the range horizontal while allowing the single control’s vertical exploration. Thumb collision and minimum separation remain actual explicit policies. |
| Keyboard and non-drag input | Reference arrows, Home/End and Space-plus-arrows are not this pin’s complete keyboard contract: installed largeStep uses Page keys and Shift-plus-arrows. Test those actual controls, track press, native range input and an exact-entry alternative. Keep focus, endpoint identity, disabled guards and immediate effects coherent. |
Working single, range, vertical and RTL inputs
Local sample level: 4 of 10 units. Keyboard arrows and the number input provide alternatives to dragging.
English-only immediate preview. Level changes type size from 1rem to 2rem.
Local range: 2 to 8 units. Endpoints cannot cross; minimum separation is one step.
- Local record at 2 sample units
- Local record at 5 sample units
- Local record at 8 sample units
No committed local adjustment. These values have no authority or proficiency meaning.
'use client';
import { useState } from 'react';
import { Button, DirectionProvider, Slider } from '@mobtranslate/ui';
export function SliderSelectionExample() {
const [value, setValue] = useState(4);
const [range, setRange] = useState<readonly number[]>([2,8]);
const [vertical, setVertical] = useState(false);
const [rtl, setRtl] = useState(false);
const [disabled, setDisabled] = useState(false);
const [committed, setCommitted] = useState('No committed local adjustment.');
const direction = rtl ? 'rtl' : 'ltr';
const samples=[0,2,5,8,10].filter(unit=>unit>=range[0]&&unit<=range[1]);
return <div data-slider-lab className="space-y-4">
<div className="flex flex-wrap gap-3">{[{label:'Vertical orientation', checked:vertical, setter:setVertical}, {label:'RTL interaction direction', checked:rtl, setter:setRtl}, {label:'Disable local sliders', checked:disabled, setter:setDisabled}].map(({label, checked, setter}) => <label className="flex gap-2 min-h-11 items-center" key={label}><input type="checkbox" checked={checked} onChange={event => setter(event.target.checked)} />{label}</label>)}</div>
<DirectionProvider direction={direction}><div dir={direction} className="space-y-4">
<p dir="auto">Local sample level: {value} of 10 units. Keyboard arrows and the number input provide alternatives to dragging.</p>
<Slider data-single-slider value={value} min={0} max={10} step={1} largeStep={2} orientation={vertical ? 'vertical' : 'horizontal'} disabled={disabled} aria-label="Local sample level" getAriaValueText={(_, current) => `${current} of 10 sample units`} onValueChange={next => { if (typeof next === 'number') setValue(next); }} onValueCommitted={next => setCommitted(`Committed local level: ${String(next)}.`)} />
<label className="block">Exact local sample level <input data-exact-level type="number" className="mt-input" min="0" max="10" step="1" value={value} disabled={disabled} onChange={event => { const next = event.target.valueAsNumber; if (Number.isFinite(next) && next >= 0 && next <= 10 && Number.isInteger(next)) setValue(next); }} /></label>
<p dir="auto" data-slider-preview style={{fontSize:`${1+value/10}rem`,overflowWrap:'anywhere'}}>English-only immediate preview. Level changes type size from 1rem to 2rem.</p>
<p dir="auto">Local range: {range[0]} to {range[1]} units. Endpoints cannot cross; minimum separation is one step.</p>
<Slider data-range-slider value={range} min={0} max={10} minStepsBetweenValues={1} thumbCollisionBehavior="none" orientation="horizontal" disabled={disabled} getAriaLabel={index => index === 0 ? 'Minimum local sample level' : 'Maximum local sample level'} getAriaValueText={(_, current) => `${current} sample units`} onValueChange={next => { if (Array.isArray(next)) setRange(next); }} />
<ul data-slider-records className="guidance-list">{samples.map(unit=><li key={unit} data-slider-unit={unit}>Local record at {unit} sample units</li>)}</ul>
{!samples.length&&<p dir="auto" data-slider-empty>No local sample records are within this range. Adjust the endpoints or restore the local range.</p>}
<Button type="button" variant="outline" style={{paddingInline:12}} disabled={disabled} onClick={()=>setRange([2,8])}>Restore local range</Button>
</div></DirectionProvider>
<p role="status" dir="auto">{committed} These values have no authority or proficiency meaning.</p>
</div>;
}
API reference
| Prop / export | Contract |
|---|---|
| value / defaultValue | Number or non-empty readonly number[]; controlled / initial values. Arrays render indexed thumbs, not only the first endpoint. |
| min / max / step / largeStep | 0 / 100 / 1 / 10 primitive defaults; finite min < max, positive step; validate application data. |
| minStepsBetweenValues | 0 default; separation for range values. |
| thumbCollisionBehavior | push default; swap or none alternatives. Choose a policy appropriate to endpoint identity. |
| orientation / disabled | horizontal default / false; vertical is supported. |
| thumbAlignment | edge wrapper default; center and edge-client-only installed alternatives. |
| onValueChange / onValueCommitted | Actual value plus details; committed event is not a backend save receipt. Change details include activeThumbIndex and reason. |
| getAriaLabel | (index) → string forwarded to each thumb input; meaningful distinct range endpoint names. |
| getAriaValueText | (formattedValue, value, index) → string forwarded to each thumb input; meaningful units. |
| aria-label / aria-labelledby | Forwarded to thumb inputs as well as root; use endpoint getter for multiple meanings. |
| name / locale / format | Installed form-name and Intl formatting contracts; inspect actual payload in the consumer. |
| DirectionProvider | Exported provider with direction=ltr default or rtl; React children. Use DOM dir for layout too. |
| className / style / render / native props / ref | Installed root contract; HTMLDivElement ref; wrappers own track/thumb children. |
Keyboard, range identity and commit
- Arrow keys adjust the focused input; Home/End and Page keys use installed bounds and step behavior. Verify horizontal/vertical and RTL direction in the target environment.
- Range inputs need separately meaningful names, bounds and units. Keep logical endpoint order stable when using a collision policy; use none where minimum/maximum must not swap.
- Use onValueChange for local feedback and onValueCommitted for a commit boundary when appropriate. Neither callback proves persistence or justifies announcing a successful server save.
- The numeric sample alternative validates finite integer values in range. Do not silently turn missing or invalid measurements into zero.
- Touch assistive technology may not synthesize all slider gestures. Verify a named device/browser pair and retain an exact-entry or other usable alternative.
Failure cases
- A two-value array with one rendered thumb or unnamed endpoints.
- Root aria-label applied only to a div while the actual range input remains unnamed.
- Vertical props with horizontal-only CSS or DOM dir changed without primitive direction context.
- Thumb targets clipped at bounds, page panning blocked by a blanket touch-action:none, or focus styles on an unfocused div only.
- Treating a drag completion as a verified durable save.
Verification and release scope
- Check actual names, roles, state and focus with pointer and keyboard. Inspect the retained value and submitted FormData when relevant.
- Exercise empty, selected, mixed, disabled, read-only, invalid and pending/recovery states that apply to the control. Styling alone does not prove enforcement.
- Check both themes, long labels, 320px reflow, meaningful graphic/text contrast, reduced motion, forced colors and the actual target device.
- Use a named screen-reader/browser pair to verify spoken state. DOM assertions and local examples do not prove announcements or backend persistence.
- Selection never establishes language quality, source authority, consent, qualified review or permission. Keep that evidence explicit in the product task.