Slider
A slider is an input control that allows users to adjust a single value or a value range along a defined linear scale. It provides a draggable handle mapped to a continuous or discrete interval.
import {Slider} from "@qualcomm-ui/react/slider"Usage
Sliders allow users to select a value or range from a continuous or discrete set by moving an indicator (thumb) along a track. They are best used for settings that reflect a range of values rather than precise input.
Examples
Simple
Use the simple API to create a slider using minimal markup.
<Slider
className="sm:w-80"
defaultValue={[25]}
hint="Some contextual help here"
label="Choose a value"
/>
Composite
Build with the composite API for granular control. This API requires you to provide each subcomponent, but gives you full control over the structure and layout.
<Slider.Root className="sm:w-80" defaultValue={[25]}>
<Slider.Label>Choose a value</Slider.Label>
<Slider.ValueText />
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumbs />
</Slider.Control>
<Slider.Hint>Some contextual help here</Slider.Hint>
<Slider.Markers />
</Slider.Root>
Min/Max/Step
The min, max, and step props control the slider's value range and increments:
min: The minimum value of the slider (default is 0).max: The maximum value of the slider (default is 100).step: The increment/decrement step value (default is 1).
<Slider
className="sm:w-80"
defaultValue={[50]}
max={70}
min={20}
step={5}
/>
Origin
Use origin to control where the track is filled from for single-value sliders:
start: Fills from the start of the track to the thumb. Useful when the value represents an absolute value (default).center: Fills from the center of the track to the thumb. Useful when the value represents an offset or relative value.end: Fills from the end of the track to the thumb. Useful when the value represents an offset from the end.
<Slider
className="sm:w-80"
defaultValue={[50]}
label="Start (default)"
origin="start"
/>
<Slider
className="sm:w-80"
defaultValue={[50]}
label="Center"
origin="center"
/>
<Slider
className="sm:w-80"
defaultValue={[50]}
label="End"
origin="end"
/>
Tooltip
Use the tooltip prop to display the slider's value in a tooltip rather than above the component.
<Slider className="mt-3 sm:w-80" defaultValue={[25]} tooltip />
Display
You can customize the tooltip's content by using the composite API and passing a function to display on the SliderThumbIndicator component.
import type {ReactElement} from "react"
import {Slider} from "@qualcomm-ui/react/slider"
export function SliderTooltipDisplayDemo(): ReactElement {
return (
<Slider.Root className="mt-3 sm:w-80" defaultValue={[25, 65]}>
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumb index={0}>
<Slider.HiddenInput />
<Slider.ThumbIndicator display={(value) => `From ${value}%`} />
</Slider.Thumb>
<Slider.Thumb index={1}>
<Slider.HiddenInput />
<Slider.ThumbIndicator display={(value) => `To ${value}%`} />
</Slider.Thumb>
</Slider.Control>
<Slider.Markers />
</Slider.Root>
)
}Markers
Track Markers
By default, the component generates 11 marks based on the slider's min and max values. provide your own set of marks by passing them in an array to the marks prop.
<Slider
className="sm:w-80"
defaultValue={[30]}
marks={[20, 30, 40, 50, 60, 70]}
max={70}
min={20}
/>
Side Markers
For more compact designs, you can display the min and max markers at the ends of the track using the sideMarkers prop.
<Slider
className="sm:w-80"
defaultValue={[30]}
max={70}
min={20}
sideMarkers
/>
Range
Set the value or defaultValue prop with two values to create a range slider.
<Slider className="sm:w-80" defaultValue={[20, 50]} />
Minimum Steps Between Thumbs
To prevent overlapping thumbs, you can use the minStepsBetweenThumbs prop to set a minimum distance between them.
<Slider
className="sm:w-80"
defaultValue={[20, 50]}
minStepsBetweenThumbs={10}
/>
Display
By default, range values are displayed separated by a dash (-). You can customize this by passing a separator string or a function to the display prop.
<Slider
className="sm:w-80"
defaultValue={[20, 50]}
display={(values) => `from ${values[0]} to ${values[1]}`}
/>
Size
Set the size prop to adjust the size of the thumbs. Available sizes are sm (small) and md (medium, default).
import type {ReactElement} from "react"
import {Slider} from "@qualcomm-ui/react/slider"
export function SliderSizeDemo(): ReactElement {
return <Slider className="sm:w-80" defaultValue={[50]} size="sm" />
}Variant
Set the variant prop to adjust the visual style of the slider. Available variants are neutral and primary (default).
<Slider className="sm:w-80" defaultValue={[50]} variant="neutral" />
Hint
Use the hint prop to add additional guidance or context to the user below the slider.
<Slider className="sm:w-80" defaultValue={[50]} hint="Additional context" />
Disabled
Use the disabled prop to prevent user interaction.
<Slider className="sm:w-80" defaultValue={[50]} disabled />
Focus Callback
The onFocusChange prop allows you to listen for focus events on the slider's thumbs.
<Slider
defaultValue={[25, 75]}
onFocusChange={(e) => {
setCurrentOutput(
e.focusedIndex === -1 ? "none" : `thumb ${e.focusedIndex}`,
)
}}
/>
Controlled State
Use value and onValueChange or onValueChangeEnd to control the value of the slider. This allows for more complex interactions and integration with form libraries.
<Slider
onValueChange={(e) => {
setValue(e.value)
}}
onValueChangeEnd={(e) => {
setFinalValue(e.value)
}}
value={value}
/>
Forms
Choose the form library that fits your needs—we've built examples with React Hook Form and Tanstack Form to get you started.
React Hook Form
Use React Hook Form to handle the slider state and validation. ArkType works great for schema validation if you need it.
import type {ReactElement} from "react"
import {arktypeResolver} from "@hookform/resolvers/arktype"
import {type} from "arktype"
import {Controller, useForm} from "react-hook-form"
import {Button} from "@qualcomm-ui/react/button"
import {Slider} from "@qualcomm-ui/react/slider"
interface FormData {
minMaxNumber: number
minRange: [number, number]
}
const minMaxNumber = type("20 <= number <= 80").configure({
message: "Value must be between 20 and 80",
})
const minRange = type(["number", "number"])
.narrow((values: [number, number]) => Math.abs(values[0] - values[1]) >= 30)
.configure({
message: "Range must be at least 30",
})
const FormSchema = type({
minMaxNumber,
minRange,
})
export function SliderReactHookFormDemo(): ReactElement {
const {control, handleSubmit} = useForm<FormData>({
defaultValues: {
minMaxNumber: 30,
minRange: [30, 70],
},
mode: "onChange",
resolver: arktypeResolver(FormSchema),
})
return (
<form
className="flex flex-col gap-10 sm:w-80"
onSubmit={(e) => {
void handleSubmit((data) => console.log(data))(e)
}}
>
<Controller
control={control}
name="minMaxNumber"
render={({
field: {onChange, value, ...fieldProps},
fieldState: {error},
}) => {
return (
<Slider
errorText={error?.message}
hint="Between 20 and 80"
invalid={!!error?.message}
label="Select a value"
onValueChange={({value}: {value: number[]}) => {
return onChange(value[0])
}}
value={[value]}
{...fieldProps}
/>
)
}}
/>
<Controller
control={control}
name="minRange"
render={({
field: {onChange, value, ...fieldProps},
fieldState: {error},
}) => {
return (
<Slider
errorText={error?.message}
hint="At least 30"
invalid={!!error?.message}
label="Select a range"
onValueChange={({value}: {value: number[]}) => {
return onChange(value)
}}
value={value}
{...fieldProps}
/>
)
}}
/>
<Button
className="mt-1"
emphasis="primary"
size="sm"
type="submit"
variant="fill"
>
Submit
</Button>
</form>
)
}Tanstack Form
Tanstack Form handles slider validation with its built-in validators.
import type {ReactElement} from "react"
import {useForm} from "@tanstack/react-form"
import {Button} from "@qualcomm-ui/react/button"
import {Slider} from "@qualcomm-ui/react/slider"
interface FormData {
minMaxNumber: number
minRange: [number, number]
}
const defaultFormData: FormData = {
minMaxNumber: 30,
minRange: [30, 70],
}
const minmaxErrorMessage = "Value must be between 20 and 80"
const minRangeErrorMessage = "Range must be at least 30"
export function SliderTanstackFormDemo(): ReactElement {
const form = useForm({
defaultValues: defaultFormData,
onSubmit: (data) => console.log(data.value),
})
const isInRange = (value: number) => value >= 20 && value <= 80
const isRangeAtLeast30 = (value: [number, number]) =>
Math.abs(value[0] - value[1]) >= 30
return (
<form
className="flex flex-col gap-10 sm:w-80"
onSubmit={(event) => {
event.preventDefault()
event.stopPropagation()
void form.handleSubmit()
}}
>
<form.Field
name="minMaxNumber"
validators={{
onChange: ({value}) =>
isInRange(value) ? undefined : minmaxErrorMessage,
onSubmit: ({value}) =>
isInRange(value) ? undefined : minmaxErrorMessage,
}}
>
{({handleChange, name, state}) => {
const fieldError =
state.meta.errorMap["onChange"] || state.meta.errorMap["onSubmit"]
return (
<Slider
errorText={fieldError}
hint="Between 20 and 80"
invalid={!!fieldError}
label="Select a value"
name={name}
onValueChange={({value}) => handleChange(value[0])}
value={[state.value]}
/>
)
}}
</form.Field>
<form.Field
name="minRange"
validators={{
onChange: ({value}) =>
isRangeAtLeast30(value) ? undefined : minRangeErrorMessage,
onSubmit: ({value}) =>
isRangeAtLeast30(value) ? undefined : minRangeErrorMessage,
}}
>
{({handleChange, name, state}) => {
const fieldError =
state.meta.errorMap["onChange"] || state.meta.errorMap["onSubmit"]
return (
<Slider
errorText={fieldError}
hint="At least 30"
invalid={!!fieldError}
label="Select a range"
name={name}
onValueChange={({value}) =>
handleChange(value as [number, number])
}
value={state.value}
/>
)
}}
</form.Field>
<Button
className="mt-1"
emphasis="primary"
size="sm"
type="submit"
variant="fill"
>
Submit
</Button>
</form>
)
}Tanstack form also supports ArkType.
Composite API and Shortcuts
The Slider component provides a composite API should you need more control over the slider's behavior and appearance.
Anatomy
The composite API is based on the following components:
Slider.Root: The main component that wraps the slider subcomponents.Slider.Label: The slider main label.Slider.ValueText: The slider current value as text.Slider.Control: The container for the thumb(s) and its track and range.Slider.Track: The track along which the thumb(s) move.Slider.Range: The filled portion of the track.Slider.ThumbsorSlider.Thumb + Slider.HiddenInput: The draggable handle(s).Slider.ThumbIndicator: The thumb tooltip.Slider.Hint: The hint text below the slider.Slider.ErrorText: The error message below the slider.Slider.MarkersorSlider.MarkerGroup + Slider.Marker: The markers along the track.
Thumbs
If you need more control over the thumbs, you can use the Slider.Thumb component instead of the Slider.Thumbs shortcut.
Also note that Slider.Thumbs will automatically create a range slider when the slider's value or defaultValue is set accordingly.
import {Slider} from "@qualcomm-ui/react/slider"
function RangeSlider() {
return (
<Slider.Root defaultValue={[25, 50]}>
<Slider.ValueText />
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumb index={0} name="min">
<Slider.HiddenInput />
</Slider.Thumb>
<Slider.Thumb index={1} name="max">
<Slider.HiddenInput />
</Slider.Thumb>
</Slider.Control>
</Slider.Root>
)
}Thumbs with tooltips
Setting the tooltip prop on the Slider.Thumbs component is the equivalent of the following composition:
import {Slider} from "@qualcomm-ui/react/slider"
function SliderWithTooltip() {
return (
<Slider.Root defaultValue={[25, 50]}>
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumb index={0}>
<Slider.HiddenInput />
<Slider.ThumbIndicator />
</Slider.Thumb>
<Slider.Thumb index={1}>
<Slider.HiddenInput />
<Slider.ThumbIndicator />
</Slider.Thumb>
</Slider.Control>
</Slider.Root>
)
}Markers
The Markers component is a convenient shortcut to display custom track markers. But you can also create your own markers using the Slider.MarkerGroup and Slider.Marker components.
import {Slider} from "@qualcomm-ui/react/slider"
function SliderWithCustomMarkers() {
return (
<Slider.Root defaultValue={[25, 50]}>
<Slider.ValueText />
<Slider.Control>
<Slider.Track>
<Slider.Range />
</Slider.Track>
<Slider.Thumbs />
</Slider.Control>
<Slider.MarkerGroup>
<Slider.Marker value={0}>0</Slider.Marker>
<Slider.Marker value={25}>25</Slider.Marker>
<Slider.Marker value={50}>50</Slider.Marker>
<Slider.Marker value={75}>75</Slider.Marker>
<Slider.Marker value={100}>100</Slider.Marker>
</Slider.MarkerGroup>
</Slider.Root>
)
}Explorer
Component Anatomy
Hover to highlight, click to view API
API
<Slider>
The Slider extends the Slider.Root with the following props:
| string
| ((
value: number[],
) => ReactNode)
Omit<
SliderMarkerProps,
'value'
>
number[]
SliderMaxPropsSliderMinPropsbooleanOmit<
SliderThumbProps,
'index' | 'tooltip'
>
booleanComposite API
<Slider.Root>
| string
| string[]
| string
| string[]
id of the elements that labels each slider thumb. Useful for providing an
accessible name to the slidernumber[]
'ltr' | 'rtl'
booleanstring({
index: number
value: number
}) => string
() =>
| Node
| ShadowRoot
| Document
booleannumbernumbernumberminStepsBetweenThumbs * step should reflect the gap between the thumbs.-
step: 1 and minStepsBetweenThumbs: 10 => gap is 10-
step: 10 and minStepsBetweenThumbs: 2 => gap is 20| string
| string[]
- For a single thumb, pass a string.
- For two thumbs (range slider), pass the two names in an array or a single name to be prefixed with its index (e.g., "slider" → "slider0", "slider1")
({
focusedNode: T
focusedValue: string
}) => void
({
value: number[]
}) => void
({
value: number[]
}) => void
| 'horizontal'
| 'vertical'
| 'start'
| 'end'
| 'center'
- "start": Useful when the value represents an absolute value
- "center": Useful when the value represents an offset (relative)
- "end": Useful when the value represents an offset from the end
boolean| ReactElement
| ((
props: object,
) => ReactElement)
'sm' | 'md'
number| 'contain'
| 'center'
-
center: the thumb will extend beyond the bounds of the slider track.-
contain: the thumb will be contained within the bounds of the track.{
height: number
width: number
}
number[]
| 'primary'
| 'neutral'
data-disableddata-draggingdata-focusdata-invaliddata-orientationOrientationdata-readonlydata-slider-part'root'style<Slider.Label>
<label> element by default.string| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-draggingdata-focusdata-invaliddata-orientationOrientationdata-readonlydata-slider-part'label'style<Slider.ValueText>
<output> element by default.| string
| ((
value: number[],
) => ReactNode)
string| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-focusdata-invaliddata-orientationOrientationdata-readonlydata-slider-part'value-text'<Slider.Control>
<div> element
by default.string| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-draggingdata-focusdata-invaliddata-orientationOrientationdata-readonlydata-slider-part'control'style<Slider.Track>
<div> element by default.string| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-draggingdata-focusdata-invaliddata-orientationOrientationdata-readonlydata-slider-part'track'style<Slider.Range>
<div> element by default.string| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-draggingdata-focusdata-invaliddata-orientationOrientationdata-readonlydata-slider-part'range'style<Slider.Thumbs> shortcut
Omit<
SliderThumbProps,
'index' | 'tooltip'
>
boolean<Slider.Thumb>
<div> element by default.numberstringstring| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-draggingdata-focusdata-indexnumberdata-namestringdata-orientationOrientationdata-readonlydata-slider-part'thumb'styletabIndexnumber<Slider.ThumbIndicator>
<div> element by
default.(
value: number,
) => ReactNode
string| ReactElement
| ((
props: object,
) => ReactElement)
<Slider.HiddenInput>
<input> element
by default.stringhiddenboolean<Slider.Markers> shortcut
stringOmit<
SliderMarkerProps,
'value'
>
number[]
min and max slider values.<Slider.MarkerGroup>
<div> element by default.string| ReactElement
| ((
props: object,
) => ReactElement)
<Slider.Marker>
<span> element by default.numberstring| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-orientationOrientationdata-readonlydata-slider-part'marker'data-state| 'over-value'
| 'under-value'
| 'at-value'
data-valuenumberstyle<Slider.Hint>
<span> element by default.string| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-draggingdata-invaliddata-orientationOrientationdata-readonlydata-slider-part'hint'hiddenboolean<Slider.ErrorText>
<span> element by default.string| ReactElement
| ((
props: object,
) => ReactElement)
data-disableddata-draggingdata-invaliddata-orientationOrientationdata-slider-part'error-text'hiddenboolean
<div>element by default.