Slider
An accessible, token-based range control with numeric, centered, predefined-option, and color-range modes.
Slider is a single-value range control for settings such as volume, opacity, zoom, temperature, crop size, discrete presets, and color selection. It wraps a native input type="range", so keyboard and screen-reader behavior follow the platform while the visual track uses the framework tokens.
The component supports controlled and uncontrolled state, custom units, three visual sizes, disabled state, formatted values, initialization deltas, commit callbacks, centered negative/positive ranges, finite option lists, and interpolated color ranges.
Basic usage
<Slider aria-label="Volume" label="Volume" defaultValue={42} />Use value and onChange for controlled state. The callback receives the numeric value and the native change event.
const [volume, setVolume] = useState(42);
<Slider
label="Volume"
max={100}
value={volume}
onChange={setVolume}
valueFormatter={(value) => `${value}%`}
/>Ranges, steps, and sizes
min, max, and step define the range. size supports exactly xs (extra small), sm (small), and default. There is no large Slider size.
<Slider size="xs" aria-label="Opacity" max={1} step={0.05} defaultValue={0.65} valueFormatter={(value) => `${Math.round(value * 100)}%`} />
<Slider size="sm" label="Temperature" min={-10} max={40} step={1} defaultValue={21} valueFormatter={(value) => `${value}°C`} />
<Slider size="default" label="Zoom" min={25} max={200} step={5} defaultValue={100} valueFormatter={(value) => `${value}%`} />Accessibility and states
Provide aria-label when no visible string label is supplied. The native range control supports Arrow keys, Page Up/Down, Home, and End. Use disabled to prevent changes while preserving the current value.
showValue displays an <output> next to the label. Use valueFormatter to add units or a localized representation.
In uncontrolled mode, the track fill and current value update internally while the component is dragged. In controlled mode, the parent must update value from onChange to keep the thumb and fill synchronized. Values are clamped to the active range.
Initial value, delta, and commit handling
Use showInitialValue to keep the value captured at initialization visible and showDelta to show the signed difference from that value. The initial value is value when controlled, otherwise defaultValue, and it remains stable during the component lifetime.
Use onChangeEnd when work should happen only after the user finishes dragging or keyboard adjustment. It fires after pointer release, after a keyboard interaction, or when the slider loses focus. It receives the final numeric value and the native change event.
<Slider
label="Temperature"
min={-10}
max={40}
defaultValue={21}
showInitialValue
showDelta
valueFormatter={(value) => `${value}°C`}
onChangeEnd={(value) => saveTemperature(value)}
/>Centered ranges
Set mode="centered" to make zero the neutral origin. Use a negative min and a positive max; the filled track then grows from zero toward the negative or positive side.
const [balance, setBalance] = useState(0);
<Slider
label="Balance"
mode="centered"
min={-100}
max={100}
value={balance}
onChange={setBalance}
valueFormatter={(value) => value === 0 ? 'Center' : `${value > 0 ? '+' : ''}${value}`}
/>Centered mode supports any negative minimum and positive maximum, including asymmetric ranges such as min={-30} and max={70}. If the range does not cross zero, the component falls back to the normal linear fill.
min, max, and step still control the numeric values in centered mode. For example, min={-1}, max={1}, and step={0.01} creates hundredths around zero.
Predefined options
Use mode="options" when the slider should move through a finite list instead of a numeric range. The input value is the zero-based option index. Options can be numbers, strings, or objects with value, label, and an optional color. Use optionFormatter for custom display and onOptionChange to receive the selected option object.
<Slider
label="Density"
mode="options"
options={[
{ value: 'compact', label: 'Compact' },
{ value: 'comfortable', label: 'Comfortable' },
{ value: 'spacious', label: 'Spacious' },
]}
defaultValue={1}
onOptionChange={(option) => setDensity(option.value)}
/>The slider position in options mode is the zero-based array index. Therefore defaultValue={0} selects the first option, defaultValue={1} selects the second, and so on. onChange receives the index, while onOptionChange receives the normalized option object and its index. When an option has a color, the shared color swatch can display it.
Color ranges
Pass colorRange to create a slider that interpolates between two colors. The thumb and a color swatch next to the selected value show the current color. showColorSwatch={false} hides the swatch while preserving the color track.
colorRange.from and colorRange.to accept CSS color strings. Hex colors are interpolated directly; other CSS color formats use the browser's color-mix() function. The range is visual and does not change the numeric value or onChange contract.
<Slider
label="Accent color"
min={0}
max={100}
defaultValue={65}
colorRange={{ from: '#2563eb', to: '#f97316' }}
valueFormatter={(value) => `${value}%`}
/>API reference
| Property | Type | Default | Description |
|---|---|---|---|
value | number | — | Controlled value. |
defaultValue | number | 0 | Initial uncontrolled value. |
min | number | 0 | Minimum value. |
max | number | 100 | Maximum value. |
step | number | 1 | Keyboard and pointer increment. |
mode | 'linear' | 'centered' | 'options' | linear | Linear fill, fill from zero in both directions, or a finite options list. |
options | Array<SliderOption | string | number> | — | Finite values used by mode="options". |
optionFormatter | (option, index) => ReactNode | — | Custom display for the selected option. |
onOptionChange | (option, index, event) => void | — | Called when an options slider selects a new option. |
colorRange | { from: string; to: string } | — | Creates a color interpolation track and selected-color thumb. |
showColorSwatch | boolean | true | Shows the selected color square when a color range or option color is present. |
onChange | (value, event) => void | — | Called with the numeric value and native change event. |
onChangeEnd | (value, event) => void | — | Called once after a completed pointer, keyboard, or focus interaction. |
label | ReactNode | — | Visible label. |
aria-label | string | — | Accessible name when the visible label is not enough. |
showValue | boolean | true | Shows the formatted value. |
showInitialValue | boolean | false | Shows the initialization value beside the current value. |
showDelta | boolean | false | Shows the signed difference from the initialization value. |
valueFormatter | (value) => ReactNode | identity | Formats the displayed value. |
size | 'xs' | 'sm' | 'default' | default | Track and thumb size. xs is extra small, sm is small. |
disabled | boolean | false | Disables interaction. |
id | string | generated | ID shared by label, output, and input. |
className | string | — | Additional root class. |
Exported helper types
| Type | Shape | Description |
|---|---|---|
SliderSize | 'xs' | 'sm' | 'default' | Supported visual sizes. |
SliderMode | 'linear' | 'centered' | 'options' | Slider operating mode. |
SliderOption | { value: string | number; label?: ReactNode; color?: string } | Normalized predefined option. |
SliderColorRange | { from: string; to: string } | Start and end colors for interpolation. |
Design tokens
The track, fill, thumb, focus ring, surface, typography, and spacing use shared tokens such as --mm-color-primary, --mm-color-control-filled, --mm-color-surface, --mm-color-focus, --mm-color-text, and --mm-color-muted-text. Scope token overrides to a theme container to create a custom slider without changing the component API.