Slider
A Slider component allows users to select one or multiple values from a defined range by dragging one or more handles along a track.
Ready to use
The Slider component allows users to select one or multiple values from a defined range by dragging one or more handles along a track. It’s commonly used for adjusting numeric values, setting limits, or selecting ranges in a visual and intuitive way, and provides immediate visual feedback when users benefit from seeing relative positions rather than entering precise numbers manually.
Anatomy
Section titled “Anatomy”- Indicator
- Progress
- Handle
- Value
When to use it
Section titled “When to use it”Use a slider when:
- Users need to adjust a value within a known, limited range.
- You want to provide a quick and interactive way to modify settings.
- Visual comparison across a range adds clarity (price filters, time ranges).
Avoid a slider when:
- Users must enter highly precise or arbitrary values.
- The range is very large or undefined.
- Accessibility or keyboard-only input is the primary interaction — pair the slider with an input field in that case.
Properties
Section titled “Properties”State
Rested, Active, and Disabled. Active swaps the handle’s fill while the user drags or presses; Disabled mutes the handle and progress fill but keeps the indicator visible so the user knows what’s there.
Handles
Single handle for one value (volume, brightness, zoom), or multiple handles for thresholds and ranges (price filter min/max). The handle size and border treatment stay identical regardless of handle count.
Variant
Three variants on the indicator itself: Default uses the neutral emphasis fill, Range uses a contrast fill for the inactive segments to highlight the selected range, and EV uses the EV brand colour for charging or efficiency tools.
Platform considerations
Section titled “Platform considerations”Desktop
Mouse and keyboard both work — arrow keys move the thumb in discrete steps, Home/End jump to the bounds. Pair with an input field when users need precise entry alongside the visual.
Tablet
Touch and pointer both work. Keep the thumb size large enough that fingers can grab it without overlapping adjacent handles.
Mobile
Touch-first. The 24 px thumb stays comfortable to grab; consider pairing with an input field when the value needs precise editing on a small screen.
Best practices
Section titled “Best practices”Sliders trade precision for feel — make the current value visible so users know what they’ve picked.
Do
Provide clear labels for the slider and its values (min, max, current), use sensible defaults that reflect common or recommended values, show the current value near the handle or in a linked input, snap to steps when values should be discrete (5, 10, 15), and use range sliders when users need to define both minimum and maximum bounds.
Don't
Don’t use sliders for precise data entry (use input fields instead), don’t overload the slider with too many handles, don’t hide value feedback (users should always know what they’re selecting), don’t rely on colour alone to communicate state or limits, and don’t make sliders too small to interact with comfortably.
Content guidelines
Section titled “Content guidelines”Label the slider with the dimension it adjusts (“Price range”, “Brightness”) and pair it with a clearly visible value read-out. Use the same units in the label and the read-out — “€20,000 – €40,000” reads better than mixing currency symbols and “k” abbreviations across the slider and its label. For range sliders, name both ends (“Min”, “Max”) near the handles so users can tell them apart without guessing.
Elements
Section titled “Elements”<div class="tng-slider"> <div class="tng-slider-track"></div></div>Labels
Section titled “Labels”<div class="tng-slider"> <div class="tng-slider-labels"> <div style="--tng-slider-label-value: 0.5">50%</div> </div> <div class="tng-slider-bar"> <div class="tng-slider-track"></div> <div class="tng-slider-thumb" style="--tng-slider-thumb-value: 0.5" ></div> </div> <div class="tng-slider-labels"> <div style="--tng-slider-label-value: 0">0%</div> <div style="--tng-slider-label-value: 0.5">50%</div> <div style="--tng-slider-label-value: 1">100%</div> </div></div>Progress
Section titled “Progress”<div class="tng-slider"> <div class="tng-slider-bar"> <div class="tng-slider-track"></div> <div class="tng-slider-progress" style=" --tng-slider-progress-start: 0.5; --tng-slider-progress-end: 0.75; " ></div> </div></div><div class="tng-slider"> <div class="tng-slider-bar"> <div class="tng-slider-track"></div> <div class="tng-slider-thumb"></div> <div class="tng-slider-thumb" style="--tng-slider-thumb-value: 0.25" ></div> <div class="tng-slider-thumb" style="--tng-slider-thumb-value: 0.5" ></div> </div></div>Active
Section titled “Active”<div class="tng-slider"> <div class="tng-slider-bar"> <div class="tng-slider-track"></div> <div class="tng-slider-thumb is-active" style="--tng-slider-thumb-value: 0.5" ></div> </div></div>Disabled
Section titled “Disabled”<div class="tng-slider"> <div class="tng-slider-bar"> <div class="tng-slider-track"></div> <div class="tng-slider-thumb is-disabled" style="--tng-slider-thumb-value: 0.5" ></div> </div></div>Variants
Section titled “Variants”<div class="tng-slider"> <div class="tng-slider-track"></div></div><div class="tng-slider is-ev"> <div class="tng-slider-track"></div></div><div class="tng-slider is-range"> <div class="tng-slider-track"></div></div><div class="tng-slider is-temperature"> <div class="tng-slider-track"></div></div>Range Input
Section titled “Range Input”<div class="tng-slider"> <input style="--tng-slider-progress-value: 0.5" type="range" /></div>With datalist
Section titled “With datalist”A <datalist> linked via list / id provides snap-to-step behavior on the native range input. Hide it with .sr-only since rendering is inconsistent across browsers, and use a separate .tng-slider-labels div with role="presentation" for the visual labels.
<div class="tng-slider"> <input list="slider-options" type="range" min="0" max="100" step="25" /> <datalist class="sr-only" id="slider-options"> <option value="0"></option> <option value="25"></option> <option value="50"></option> <option value="75"></option> <option value="100"></option> </datalist> <div class="tng-slider-labels" role="presentation"> <div style="--tng-slider-label-value: 0">0%</div> <div style="--tng-slider-label-value: 0.25">25%</div> <div style="--tng-slider-label-value: 0.5">50%</div> <div style="--tng-slider-label-value: 0.75">75%</div> <div style="--tng-slider-label-value: 1">100%</div> </div></div>Disabled
Section titled “Disabled”<div class="tng-slider"> <input disabled type="range" /></div>Import the Slider component from @tmedxp/react-components. For cases where you don’t need the built-in labels, use BasicSlider instead.
Properties
Section titled “Properties”Single thumb
Section titled “Single thumb”| Prop | Type | Description | Required |
|---|---|---|---|
bottomLabels |
[string, string, string] |
Three scale labels displayed below the track. | ✅ |
defaultValue |
number |
Initial value of the slider. | |
value |
number |
Controlled value of the slider. | |
min |
number |
Minimum value. | |
max |
number |
Maximum value. | |
step |
number |
Step increment. | |
steps |
number[] |
Discrete snap values (alternative to step). |
|
disabled |
boolean |
Disables the slider. | |
theme |
'default' | 'ev' | 'temperature' |
Visual theme variant. | |
ariaLabel |
string |
Accessible label for the thumb. | |
ariaLabelledby |
string |
ID of the element labelling the slider. | |
showProgress |
boolean |
Show the progress fill on the track. | |
formatValueText |
(value: number) => string |
Formats aria-valuetext for assistive technologies. |
|
onChange |
(value: number) => void |
Callback when the value changes. |
Multi thumb (range)
Section titled “Multi thumb (range)”| Prop | Type | Description | Required |
|---|---|---|---|
ariaLabels |
[string, string] |
Accessible labels for each thumb. | ✅ |
topLabels |
[string, string, string] |
Three scale labels displayed above the track. | ✅ |
defaultValue |
[number, number] |
Initial values for both thumbs. | |
value |
[number, number] |
Controlled values for both thumbs. | |
min |
number |
Minimum value. | |
max |
number |
Maximum value. | |
step |
number |
Step increment. | |
steps |
number[] |
Discrete snap values (alternative to step). |
|
disabled |
boolean |
Disables the slider. | |
theme |
'default' | 'ev' | 'temperature' |
Visual theme variant. | |
topIcons |
[SliderIcon?, SliderIcon?, SliderIcon?] |
Icons displayed alongside top labels. | |
showProgress |
boolean |
Show the progress fill on the track. | |
formatValueText |
(value: number) => string |
Formats aria-valuetext for assistive technologies. |
|
onChange |
(value: [number, number]) => void |
Callback when either value changes. |
Examples
Section titled “Examples”Single thumb
Section titled “Single thumb”import { Slider } from '@tmedxp/react-components';
const SliderExample = () => { return ( <Slider defaultValue={50} ariaLabel="Percentage" formatValueText={(value) => `${value}%`} bottomLabels={['0%', '50%', '100%']} /> );};With progress
Section titled “With progress”import { Slider } from '@tmedxp/react-components';
const ProgressSlider = () => { return ( <Slider defaultValue={60} showProgress ariaLabel="Percentage" formatValueText={(value) => `${value}%`} bottomLabels={['0%', '50%', '100%']} /> );};EV theme
Section titled “EV theme”import { Slider } from '@tmedxp/react-components';
const EvSlider = () => { return ( <Slider defaultValue={80} theme="ev" ariaLabel="Battery charge level" formatValueText={(value) => `${value}%`} bottomLabels={['0%', '50%', '100%']} /> );};Temperature theme
Section titled “Temperature theme”import { Slider } from '@tmedxp/react-components';
const TemperatureSlider = () => { return ( <Slider defaultValue={18} min={-20} max={30} theme="temperature" ariaLabel="Temperature" formatValueText={(value) => `${value}°C`} bottomLabels={['-20°C', '5°C', '30°C']} /> );};With discrete steps
Section titled “With discrete steps”import { Slider } from '@tmedxp/react-components';
const SteppedSlider = () => { return ( <Slider defaultValue={50} steps={[0, 25, 50, 75, 100]} ariaLabel="Quality" formatValueText={(value) => `${value}%`} bottomLabels={['Low', 'Mid', 'High']} /> );};Disabled
Section titled “Disabled”import { Slider } from '@tmedxp/react-components';
const DisabledSlider = () => { return ( <Slider defaultValue={40} disabled ariaLabel="Percentage" formatValueText={(value) => `${value}%`} bottomLabels={['0%', '50%', '100%']} /> );};Multi thumb (range)
Section titled “Multi thumb (range)”import { Slider } from '@tmedxp/react-components';
const RangeSliderExample = () => { return ( <Slider defaultValue={[25, 75]} ariaLabels={['Minimum', 'Maximum']} topLabels={['Low', 'Mid', 'High']} /> );};Multi thumb with icons
Section titled “Multi thumb with icons”import { Slider } from '@tmedxp/react-components';
const RangeSliderWithIcons = () => { return ( <Slider defaultValue={[25, 75]} ariaLabels={['Minimum', 'Maximum']} topLabels={['Low', 'Mid', 'High']} topIcons={['highway', 'tree', 'check']} /> );};Multi thumb temperature
Section titled “Multi thumb temperature”import { Slider } from '@tmedxp/react-components';
const TemperatureRangeSlider = () => { return ( <Slider defaultValue={[-5, 20]} min={-20} max={30} theme="temperature" ariaLabels={['Minimum temperature', 'Maximum temperature']} formatValueText={(value) => `${value}°C`} topLabels={['Cold', 'Mild', 'Hot']} /> );};Controlled value
Section titled “Controlled value”import { useState } from 'react';import { Slider } from '@tmedxp/react-components';
const ControlledSlider = () => { const [value, setValue] = useState(50);
return ( <> <Slider value={value} onChange={setValue} ariaLabel="Volume" formatValueText={(v) => `${v}%`} bottomLabels={['Mute', '50%', 'Max']} /> <p>Current value: {value}%</p> </> );};The slider follows the WAI-ARIA Slider pattern — one or more thumbs the user moves through a range of values with the keyboard or pointer — and meets WCAG 2.1 AA. An accessible slider keeps each thumb reachable and operable from the keyboard, exposes its current, minimum, and maximum values to assistive technology, and carries an accessible name — so the value it holds is clear whether the user sees it, hears it, or drives it by keyboard.
For designers
Section titled “For designers”- The thumb, track, progress fill, and value labels must maintain accessible contrast on every supported surface. Source: WCAG 1.4.3 Contrast (Minimum).
- The current value and range limits must never be conveyed by colour alone — pair the fill with the visible value read-out, labels, or the thumb’s position. Source: WCAG 1.4.1 Use of Color.
- The thumb’s focus style must stay visible, meet contrast requirements on every supported surface, and be clearly distinguishable from hover and the active (dragging) state. Source: WCAG 2.4.7 Focus Visible.
- Use semantic colour tokens (e.g.
foreground/neutral/default) rather than fixed values, so the component adapts correctly across themes and surfaces.
For developers
Section titled “For developers”Build the slider on a native <input type="range"> — it brings slider semantics and full keyboard support for free, with no extra ARIA. Fall back to a custom role="slider" only when a native input genuinely can’t be used; then you own every ARIA property and key binding yourself. A native disabled attribute takes the slider out of the tab order. A range slider is two <input type="range"> thumbs sharing one track — give each its own accessible name (e.g. Minimum and Maximum) so assistive technology tells them apart. The per-element name and value wiring is in the Labelling elements section below.
Keyboard interaction
Section titled “Keyboard interaction”| Key | Action |
|---|---|
| → / ↑ | Increase the value by one step |
| ← / ↓ | Decrease the value by one step |
| Home / End | Jump to the minimum / maximum value |
| Page Up / Page Down | Increase / decrease by a larger step |
A native <input type="range"> handles all of the above automatically; a custom role="slider" must implement each key itself.
Interactive targets must be at least 24 px so the thumb stays comfortable to acquire — larger is better for touch. Source: WCAG 2.5.8 Target Size (Minimum).
Labelling elements
Section titled “Labelling elements”Give the slider the role, name, and value semantics assistive technology needs. The anatomy below marks the two parts that carry that story — the thumb the user drives and the value labels around the track.
- Slider thumb
- Value labels
Slider thumb
A native <input type="range"> carries role="slider" implicitly, with aria-valuemin, aria-valuemax, and aria-valuenow derived from its min, max, and value. Name it with <label for>, aria-labelledby, or aria-label — the visual value labels around the track don’t name the control. Add aria-valuetext when the raw number isn’t meaningful, so assistive technology announces the formatted value (aria-valuetext="€20,000" rather than 20000). On a custom role="slider", set all of these yourself, plus aria-disabled="true" wherever the native disabled attribute would otherwise apply.
Value labels
The scale labels around the track (.tng-slider-labels) are decorative duplicates of the value. Mark them role="presentation" so they aren’t announced separately from the thumb’s value. A <datalist> wired for snap-to-step should be .sr-only — its list / id linkage still works while its inconsistent native rendering stays hidden.
Source: WCAG 4.1.2 Name, Role, Value.