Skip to content
Destyler UI Destyler UI Destyler UI

Slider

A slider allows users to make selections from a range of values. Think of it as a custom <input type='range'/> with the ability to achieve custom styling and accessibility.

Features

Install

Install the component from your command line.

Terminal window
      
        
npm install @destyler/slider @destyler/vue
Terminal window
      
        
npm install @destyler/slider @destyler/react
Terminal window
      
        
npm install @destyler/slider @destyler/svelte
Terminal window
      
        
npm install @destyler/slider @destyler/solid

Anatomy

Import all parts and piece them together.

<script setup lang="ts">
import * as slider from '@destyler/slider'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(slider.machine({ id: useId() }))
const api = computed(() => slider.connect(state.value, send, normalizeProps))
</script>
<template>
<div v-bind="api.getRootProps()" >
<div>
<label v-bind="api.getLabelProps()"></label>
<output v-bind="api.getValueTextProps()">{api.value.at(0)}</output>
</div>
<div v-bind="api.getControlProps()" >
<div v-bind="api.getTrackProps()" >
<div v-bind="api.getRangeProps()" />
</div>
<div v-bind="api.getThumbProps({ index:0 })" >
<input v-bind="api.getHiddenInputProps({ index: 0 })" >
</div>
</div>
</div>
</template>
import { normalizeProps, useMachine } from '@destyler/react'
import * as slider from '@destyler/slider'
import { useId } from 'react'
export default function Slider() {
const [state, send] = useMachine(slider.machine({
id: useId(),
}))
const api = slider.connect(state, send, normalizeProps)
return (
<div {...api.getRootProps()} >
<div>
<label {...api.getLabelProps()}></label>
<output {...api.getValueTextProps()}>{api.value.at(0)}</output>
</div>
<div {...api.getControlProps()} >
<div {...api.getTrackProps()} >
<div {...api.getRangeProps()} />
</div>
<div {...api.getThumbProps({ index: 0 })} >
<input {...api.getHiddenInputProps({ index: 0 })} />
</div>
</div>
</div>
)
}
<script lang="ts">
import * as slider from '@destyler/slider'
import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(slider.machine({ id }))
const api = $derived(slider.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()}>
<div>
<label {...api.getLabelProps()}></label>
<output {...api.getValueTextProps()}>{api.value.at(0)}</output>
</div>
<div {...api.getControlProps()}>
<div {...api.getTrackProps()} >
<div {...api.getRangeProps()} ></div>
</div>
<div {...api.getThumbProps({ index: 0 })} >
<input {...api.getHiddenInputProps({ index: 0 })} >
</div>
</div>
</div>
import * as slider from '@destyler/slider'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
export default function Slider({ className = '' }: { className?: string }) {
const [state, send] = useMachine(slider.machine({
id: createUniqueId(),
}))
const api = createMemo(() => slider.connect(state, send, normalizeProps))
return (
<div {...api().getRootProps()} >
<div>
<label {...api().getLabelProps()}></label>
<output {...api().getValueTextProps()}>{api.value.at(0)}</output>
</div>
<div {...api().getControlProps()} >
<div {...api().getTrackProps()} >
<div {...api().getRangeProps()} />
</div>
<div {...api().getThumbProps({ index: 0 })} >
<input {...api().getHiddenInputProps({ index: 0 })} />
</div>
</div>
</div>
)
}

Changing the orientation

By default, the slider is assumed to be horizontal. To change the orientation to vertical, set the orientation property in the destyler’s context to vertical.

In this mode, the slider will use the arrow up and down keys to increment/decrement its value.

const [state, send] = useMachine(
slider.machine({
orientation: "vertical",
}),
)

Setting the initial value

const [state, send] = useMachine(
slider.machine({
value: [60],
}),
)

Specifying the minimum and maximum

By default, the minimum is 0 and the maximum is 100. If that’s not what you want, you can easily specify different bounds by changing the values of the min and/or max attributes.

For example, to ask the user for a value between -10 and 10, you can use:

const [state, send] = useMachine(
slider.machine({
min: -10,
max: 10,
}),
)

Setting the value’s granularity

By default, the granularity, is 1, meaning that the value is always an integer. You can change the step attribute to control the granularity.

For example, If you need a value between 5 and 10, accurate to two decimal places, you should set the value of step to 0.01:

const [state, send] = useMachine(
slider.machine({
min: 5,
max: 10,
step: 0.01,
}),
)

Listening for changes

When the slider value changes, the onValueChange and onValueChangeEnd callbacks are invoked. You can use this to setup custom behaviors in your app.

const [state, send] = useMachine(
slider.machine({
onValueChange(details) {
console.log("value is changing to:", details)
},
onValueChangeEnd(details) {
console.log("value has changed to:", details)
},
}),
)

Changing the start position

By default, the slider’s “zero position” is usually at the start position (left in LTR and right in RTL).

In scenarios where the value represents an offset (or relative value), it might be useful to change the “zero position” to center. To do this, pass the origin context property to center.

const [state, send] = useMachine(
slider.machine({
origin: "center",
}),
)

Changing the thumb alignment

By default, the thumb is aligned to the start of the track. Set the thumbAlignment context property to contain or 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.

const [state, send] = useMachine(
slider.machine({
thumbAlignment: "center",
}),
)

const [state, send] = useMachine(
slider.machine({
thumbAlignment: "contain",
thumbSize: { width: 20, height: 20 },
}),
)

Usage within forms

To use slider within forms, use the exposed inputProps from the connect function and ensure you pass name value to the destyler’s context. It will render a hidden input and ensure the value changes get propagated to the form correctly.

const [state, send] = useMachine(
slider.machine({
name: "quantity",
}),
)

RTL Support

The slider has built-in support for RTL alignment and interaction. In the RTL mode, operations are performed from right to left, meaning, the left arrow key will increment and the right arrow key will decrement.

To enable RTL support, pass the dir: rtl context property

const [state, send] = useMachine(
slider.machine({
dir: "rtl",
}),
)

Using slider marks

To show marks or ticks along the slider track, use the exposed api.getMarkerProps() method to position the slider marks relative to the track.

<script setup lang="ts">
import * as slider from '@destyler/slider'
import { normalizeProps, useMachine } from '@destyler/vue'
import { computed, useId } from 'vue'
const [state, send] = useMachine(slider.machine({ id: useId() }))
const api = computed(() => slider.connect(state.value, send, normalizeProps))
</script>
<template>
<div v-bind="api.getRootProps()" >
<div>
<label v-bind="api.getLabelProps()"></label>
<output v-bind="api.getValueTextProps()">{api.value.at(0)}</output>
</div>
<div v-bind="api.getControlProps()" >
<div v-bind="api.getTrackProps()" >
<div v-bind="api.getRangeProps()" />
</div>
<div v-bind="api.getThumbProps({ index:0 })" >
<input v-bind="api.getHiddenInputProps({ index: 0 })" >
</div>
</div>
</div>
<div v-bind="api.getMarkerGroupProps()">
<span v-bind="api.getMarkerProps({ value: 10 })">|</span>
<span v-bind="api.getMarkerProps({ value: 30 })">|</span>
<span v-bind="api.getMarkerProps({ value: 90 })">|</span>
</div>
</template>
import { normalizeProps, useMachine } from '@destyler/react'
import * as slider from '@destyler/slider'
import { useId } from 'react'
export default function Slider() {
const [state, send] = useMachine(slider.machine({
id: useId(),
}))
const api = slider.connect(state, send, normalizeProps)
return (
<>
<div {...api.getRootProps()} >
<div>
<label {...api.getLabelProps()}></label>
<output {...api.getValueTextProps()}>{api.value.at(0)}</output>
</div>
<div {...api.getControlProps()} >
<div {...api.getTrackProps()} >
<div {...api.getRangeProps()} />
</div>
<div {...api.getThumbProps({ index: 0 })} >
<input {...api.getHiddenInputProps({ index: 0 })} />
</div>
</div>
</div>
<div {...api.getMarkerGroupProps()}>
<span {...api.getMarkerProps({ value: 10 })}>|</span>
<span {...api.getMarkerProps({ value: 30 })}>|</span>
<span {...api.getMarkerProps({ value: 90 })}>|</span>
</div>
</>
)
}
<script lang="ts">
import * as slider from '@destyler/slider'
import { normalizeProps, useMachine } from '@destyler/svelte'
const id = $props.id()
const [state, send] = useMachine(slider.machine({ id }))
const api = $derived(slider.connect(state, send, normalizeProps))
</script>
<div {...api.getRootProps()}>
<div>
<label {...api.getLabelProps()}></label>
<output {...api.getValueTextProps()}>{api.value.at(0)}</output>
</div>
<div {...api.getControlProps()}>
<div {...api.getTrackProps()} >
<div {...api.getRangeProps()} ></div>
</div>
<div {...api.getThumbProps({ index: 0 })} >
<input {...api.getHiddenInputProps({ index: 0 })} >
</div>
</div>
</div>
<div {...api.getMarkerGroupProps()}>
<span {...api.getMarkerProps({ value: 10 })}>|</span>
<span {...api.getMarkerProps({ value: 30 })}>|</span>
<span {...api.getMarkerProps({ value: 90 })}>|</span>
</div>
import * as slider from '@destyler/slider'
import { normalizeProps, useMachine } from '@destyler/solid'
import { createMemo, createUniqueId } from 'solid-js'
export default function Slider({ className = '' }: { className?: string }) {
const [state, send] = useMachine(slider.machine({
id: createUniqueId(),
}))
const api = createMemo(() => slider.connect(state, send, normalizeProps))
return (
<>
<div {...api().getRootProps()} >
<div>
<label {...api().getLabelProps()}></label>
<output {...api().getValueTextProps()}>{api.value.at(0)}</output>
</div>
<div {...api().getControlProps()} >
<div {...api().getTrackProps()} >
<div {...api().getRangeProps()} />
</div>
<div {...api().getThumbProps({ index: 0 })} >
<input {...api().getHiddenInputProps({ index: 0 })} />
</div>
</div>
</div>
<div {...api().getMarkerGroupProps()}>
<span {...api().getMarkerProps({ value: 10 })}>|</span>
<span {...api().getMarkerProps({ value: 30 })}>|</span>
<span {...api().getMarkerProps({ value: 90 })}>|</span>
</div>
</>
)
}

Styling guide

Earlier, we mentioned that each Slider part has a data-part attribute added to them to select and style them in the DOM.

Focused State

When the slider thumb is focused, the data-focus attribute is added to the root, control, thumb and label parts.

[data-part="root"][data-focus] {
/* styles for root focus state */
}
[data-part="thumb"]:focus {
/* styles for thumb focus state */
}
[data-part="control"][data-focus] {
/* styles for control focus state */
}
[data-part="track"][data-focus] {
/* styles for track focus state */
}
[data-part="range"][data-focus] {
/* styles for range focus state */
}

Disabled State

When the slider is disabled, the data-disabled attribute is added to the root, label, control and thumb.

[data-part="root"][data-disabled] {
/* styles for root disabled state */
}
[data-part="label"][data-disabled] {
/* styles for label disabled state */
}
[data-part="control"][data-disabled] {
/* styles for control disabled state */
}
[data-part="value-text"][data-disabled] {
/* styles for output disabled state */
}
[data-part="thumb"][data-disabled] {
/* styles for thumb disabled state */
}
[data-part="range"][data-disabled] {
/* styles for thumb disabled state */
}

Invalid State

When the slider is invalid, the data-invalid attribute is added to the root, track, range, label, and thumb parts.

[data-part="root"][data-invalid] {
/* styles for root invalid state */
}
[data-part="label"][data-invalid] {
/* styles for label invalid state */
}
[data-part="control"][data-invalid] {
/* styles for control invalid state */
}
[data-part="valueText"][data-invalid] {
/* styles for output invalid state */
}
[data-part="thumb"][data-invalid] {
/* styles for thumb invalid state */
}
[data-part="range"][data-invalid] {
/* styles for range invalid state */
}

Orientation

[data-part="root"][data-orientation="horizontal"] {
/* styles for horizontal or vertical */
}
[data-part="thumb"][data-orientation="horizontal"] {
/* styles for horizontal or vertical */
}
[data-part="track"][data-orientation="horizontal"] {
/* styles for horizontal or vertical */
}

Styling the markers

[data-part="marker"][data-state="at-value"] {
/* styles for when the value exceeds the marker's value */
}

Methods and Properties

Machine Context

The slider machine exposes the following context properties:

ids
Partial<{ root: string; thumb(index: number): string; hiddenInput(index: number): string; control: string; track: string; range: string; label: string; valueText: string; marker(index: number): string; }>
The ids of the elements in the range slider. Useful for composition.
aria-label
string[]
The aria-label of each slider thumb. Useful for providing an accessible name to the slider
aria-labelledby
string[]
The `id` of the elements that labels each slider thumb. Useful for providing an accessible name to the slider
name
string
The name associated with each slider thumb (when used in a form)
form
string
The associate form of the underlying input element.
value
number[]
The value of the range slider
disabled
boolean
Whether the slider is disabled
readOnly
boolean
Whether the slider is read-only
invalid
boolean
Whether the slider is invalid
onValueChange
(details: ValueChangeDetails) => void
Function invoked when the value of the slider changes
onValueChangeEnd
(details: ValueChangeDetails) => void
Function invoked when the slider value change is done
onFocusChange
(details: FocusChangeDetails) => void
Function invoked when the slider's focused index changes
getAriaValueText
(details: ValueTextDetails) => string
Function that returns a human readable value for the slider thumb
min
number
The minimum value of the slider
max
number
The maximum value of the slider
step
number
The step value of the slider
minStepsBetweenThumbs
number
The minimum permitted steps between multiple thumbs.
orientation
"vertical" | "horizontal"
The orientation of the slider
origin
"start" | "center"
The origin of the slider range - "start": Useful when the value represents an absolute value - "center": Useful when the value represents an offset (relative)
thumbAlignment
"center" | "contain"
The alignment of the slider thumb relative to the track - `center`: the thumb will extend beyond the bounds of the slider track. - `contain`: the thumb will be contained within the bounds of the track.
thumbSize
{ width: number; height: number; }
The slider thumbs dimensions
dir
"ltr" | "rtl"
The document's text/writing direction.
id
string
The unique identifier of the machine.
getRootNode
() => ShadowRoot | Node | Document
A root node to correctly resolve document in custom environments. E.x.: Iframes, Electron.

Machine API

The slider api exposes the following methods:

value
number[]
The value of the slider.
dragging
boolean
Whether the slider is being dragged.
focused
boolean
Whether the slider is focused.
setValue
(value: number[]) => void
Function to set the value of the slider.
getThumbValue
(index: number) => number
Returns the value of the thumb at the given index.
setThumbValue
(index: number, value: number) => void
Sets the value of the thumb at the given index.
getValuePercent
(value: number) => number
Returns the percent of the thumb at the given index.
getPercentValue
(percent: number) => number
Returns the value of the thumb at the given percent.
getThumbPercent
(index: number) => number
Returns the percent of the thumb at the given index.
setThumbPercent
(index: number, percent: number) => void
Sets the percent of the thumb at the given index.
getThumbMin
(index: number) => number
Returns the min value of the thumb at the given index.
getThumbMax
(index: number) => number
Returns the max value of the thumb at the given index.
increment
(index: number) => void
Function to increment the value of the slider at the given index.
decrement
(index: number) => void
Function to decrement the value of the slider at the given index.
focus
() => void
Function to focus the slider. This focuses the first thumb.

Data Attributes

Root

name
desc
data-scope
slider
data-part
root
data-disabled
Present when disabled
data-orientation
The orientation of the slider
data-dragging
Present when in the dragging state
data-invalid
Present when invalid
data-focus
Present when focused

ValueText

name
desc
data-scope
slider
data-part
value-text
data-disabled
Present when disabled
data-orientation
The orientation of the valuetext
data-invalid
Present when invalid
data-focus
Present when focused

Track

name
desc
data-scope
slider
data-part
track
data-disabled
Present when disabled
data-invalid
Present when invalid
data-dragging
Present when in the dragging state
data-orientation
The orientation of the track
data-focus
Present when focused

Thumb

name
desc
data-scope
slider
data-part
thumb
data-index
The index of the item
data-disabled
Present when disabled
data-orientation
The orientation of the thumb
data-focus
Present when focused
data-dragging
Present when in the dragging state

Range

name
desc
data-scope
slider
data-part
range
data-dragging
Present when in the dragging state
data-focus
Present when focused
data-invalid
Present when invalid
data-disabled
Present when disabled
data-orientation
The orientation of the range

Control

name
desc
data-scope
slider
data-part
control
data-dragging
Present when in the dragging state
data-disabled
Present when disabled
data-orientation
The orientation of the control
data-invalid
Present when invalid
data-focus
Present when focused

MarkerGroup

name
desc
data-scope
slider
data-part
marker-group
data-orientation
The orientation of the markergroup

Marker

name
desc
data-scope
slider
data-part
marker
data-orientation
The orientation of the marker
data-value
The value of the item
data-disabled
Present when disabled

DraggingIndicator

name
desc
data-scope
slider
data-part
dragging-indicator
data-orientation
The orientation of the draggingindicator
data-state
"open" | "closed"

Accessibility

Keyboard Interaction

name
desc
ArrowRight
Increments the slider based on defined step
ArrowLeft
Decrements the slider based on defined step
ArrowUp
Increases the value by the step amount.
ArrowDown
Decreases the value by the step amount.
PageUp
Increases the value by a larger step
PageDown
Decreases the value by a larger step
Shift + ArrowUp
Increases the value by a larger step
Shift + ArrowDown
Decreases the value by a larger step
Home
Sets the value to its minimum.
End
Sets the value to its maximum.