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.
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:
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; }>
string[]
string[]
string
string
number[]
boolean
boolean
boolean
(details: ValueChangeDetails) => void
(details: ValueChangeDetails) => void
(details: FocusChangeDetails) => void
(details: ValueTextDetails) => string
number
number
number
number
"vertical" | "horizontal"
"start" | "center"
"center" | "contain"
{ width: number; height: number; }
"ltr" | "rtl"
string
() => ShadowRoot | Node | Document
Machine API
The slider api
exposes the following methods:
number[]
boolean
boolean
(value: number[]) => void
(index: number) => number
(index: number, value: number) => void
(value: number) => number
(percent: number) => number
(index: number) => number
(index: number, percent: number) => void
(index: number) => number
(index: number) => number
(index: number) => void
(index: number) => void
() => void
Data Attributes
Root
data-scope
data-part
data-disabled
data-orientation
data-dragging
data-invalid
data-focus
ValueText
data-scope
data-part
data-disabled
data-orientation
data-invalid
data-focus
Track
data-scope
data-part
data-disabled
data-invalid
data-dragging
data-orientation
data-focus
Thumb
data-scope
data-part
data-index
data-disabled
data-orientation
data-focus
data-dragging
Range
data-scope
data-part
data-dragging
data-focus
data-invalid
data-disabled
data-orientation
Control
data-scope
data-part
data-dragging
data-disabled
data-orientation
data-invalid
data-focus
MarkerGroup
data-scope
data-part
data-orientation
Marker
data-scope
data-part
data-orientation
data-value
data-disabled
DraggingIndicator
data-scope
data-part
data-orientation
data-state
Accessibility
Keyboard Interaction
ArrowRight
ArrowLeft
ArrowUp
ArrowDown
PageUp
PageDown
Shift + ArrowUp
Shift + ArrowDown
Home
End