Skip to content

Slider

A labeled range slider paired with a synced number field for fine-tuned keyboard entry. Typed values commit on change, snapped to the step and clamped to the range.

Import

import { Slider } from "@hyzer-labs/ui"

Demo

Drag for coarse control, type for precision. The number field commits on blur or Enter, clamped to the range. Try typing 900.

let distance = $state(350);

<Slider name="distance" label="Longest throw" min={0} max={700} unit="ft" bind:value={distance} />

Props

NameTypeDefaultNote
namestringRequired. Form field name (on the range input).
labelstringRequired. Labels the range; sr-only with hideLabel.
minnumber0
maxnumber100
stepnumber1
valuenumberminBindable.
showInputbooleantrueRenders the exact-entry number field next to the slider.
ticksSliderTick[]Decorative marks under the track. See SliderTick below.
unitstringVisual suffix after the number field; rendered aria-hidden.
inputLabelstring`${label} (exact value)`Accessible name for the number field.
orientation'horizontal' | 'vertical''horizontal'Vertical uses writing-mode: vertical-lr (bottom-up growth); horizontal is unchanged.
inputPosition'start' | 'end''end'Logical on both axes: horizontal trailing/leading, vertical below/above the track.
descriptionstringHelp text below the label.
errorstringInline error message; sets the error state.
requiredbooleanfalseLabel indicator only.
disabledbooleanfalseDisables both inputs.
hideLabelbooleanfalse
classstringMerged after the hz-field hz-field--slider classes.

SliderTick

NameTypeDefaultNote
valuenumberRequired. A bare number is shorthand for an unlabeled tick. Out-of-range ticks are skipped.
labelstringSmall text under the mark.

Theme hooks

What this component promises your CSS. The reference theme styles exactly these — from @layer hz-theme, so your unlayered rules win. See Styling Components for the how.

Root class: .hz-field hz-field--slider

Data attributes

HookValuesStyles
data-state'default' | 'error' | 'disabled'The universal field state hook, on the root. Precedence is fixed: error beats disabled.
data-has-tickspresent when ticks renderOn .hz-slider-row; reserves room for the tick strip. Reflects the normalized list — out-of-range ticks are dropped — so it is not derivable from the prop.
data-has-inputpresent when the number field showsOn .hz-slider-row. The reference theme targets the parts instead, so this one is yours.
data-orientation'horizontal' | 'vertical'On .hz-slider-row; always present. Drives the writing-mode-based vertical mechanism and the theme rotation (fill, ticks). Default horizontal.
data-input-position'start' | 'end'On .hz-slider-row; always present. Logical on both axes — reorders the track vs. the number field/readout. Default end.

Custom properties

HookValuesStyles
--hz-slider-track-height<length> — default 0.375remTrack thickness.
--hz-slider-thumb-size<length> — default 1.125remThumb width and height. The fill edge tracks the thumb centre, so the fill and ticks stay aligned at any size.
--hz-slider-length<length> — default 12remThe track's block length in vertical orientation. Ignored in horizontal, where the track flexes to fill the row instead.

Part classes

HookValuesStyles
.hz-field-labelchild elementThe label.
.hz-field-requiredchild elementThe required marker.
.hz-field-descriptionchild elementThe hint text.
.hz-field-errorchild elementThe error message.
.hz-slider-rowchild elementThe track-and-readout row.
.hz-slider-trackchild elementThe painted track.
.hz-sliderthe range inputThe native input carrying the thumb.
.hz-slider-tickschild elementThe tick strip.
.hz-slider-tickchild elementOne tick.
.hz-slider-tick-labelchild elementA tick’s label.
.hz-slider-numberchild elementThe editable number field.
.hz-slider-valuechild elementThe read-only readout.
.hz-slider-unitchild elementThe unit suffix.

Accessibility

The range input is the labeled (id/for), named, form-participating control. Its slider semantics (value, min, max) are native, and arrow keys step it.

The number field is an exact-entry affordance with its own accessible name (inputLabel) and no name, so it never submits.

description and error chain into aria-describedby on the range (description first), and an error sets aria-invalid. required renders the label indicator only. aria-required is not part of the slider role's supported ARIA attributes, so it is never applied.

unit is decorative and aria-hidden. Put meaning-bearing units in the label or description instead. In vertical orientation the component stamps aria-orientation="vertical" on the range explicitly, since writing-mode does not reliably flip a native range's implicit horizontal orientation in the accessibility tree across engines and assistive tech.

References: APG Slider pattern · MDN: <input type="range">