HySlider
Usage examples on this page are written for Lit / plain HTML (
<hy-slider>). The same component ships asHySliderin@whitespaceux/harmony-react(native React 19), with typed wrappers for Angular, Solid, Svelte, and Vue. The API reference below applies to all frameworks.
A form-associated horizontal range slider for selecting a numeric value within a bounded range. Layers a custom track, fill, and thumb over a transparent native <input type="range"> to retain full accessibility and keyboard support (arrow keys, PageUp/Down, Home/End) while allowing design-system-aligned styling.
Form Integration
Implements the Form-Associated Custom Elements API — the slider's value is included in FormData when the form is submitted under its name.
Keyboard
Native range input handles all keyboard interactions:
- Arrow keys: increment / decrement by
step - PageUp / PageDown: larger jumps (browser-defined)
- Home / End: jump to
min/max
Layout
In block layout the label sits one form gap above the thumb: the row is a full control height tall (the hit area) with the track centered in it, so the row is pulled up by the transparent band above the thumb. In inline layout the row keeps its full height, lining up with other form controls.
Gradient / custom track
Use the track slot to overlay custom content (e.g., a color gradient for hue/chroma/lightness sliders). Hide the solid fill via its color hook:
hy-slider.rainbow {
--hy-slider-background-brand-indicator-rest: transparent;
}When to use
- A value where the feel matters more than the exact number: volume, opacity, a zoom level.
- A range between two bounds, with
range: a price range, a time span.
Do
- Leave the value shown unless a label at each end says enough.
- Set
stepto the smallest change that makes a difference.
When not to use
- An exact number, an open-ended range, or one so wide a pixel spans many values:
hy-number-input. - A few named options:
hy-radio-group, orhy-selectwhen space is tight. - An angle or a direction:
hy-angle-picker. - Showing a value people do not set:
hy-meterfor a measurement,hy-progressfor progress.
Accessibility: your part
- Choose a
stepthat lets keyboard users reach every meaningful value in a few presses.
Examples
Basic usage
<hy-slider label="Spacing step" min="1" max="8" value="4"></hy-slider>With hint and suffix
<hy-slider
label="Motion speed"
hint="0 = instant, 1 = languid"
min="0"
max="1"
step="0.01"
value="0.5"
suffix="s"
>
</hy-slider>Color slider with gradient track
<style>
.hue-track {
background: linear-gradient(to right in hsl longer hue, hsl(0 80% 50%), hsl(360 80% 50%));
}
</style>
<hy-slider label="Hue" min="0" max="360" value="180" suffix="°">
<div slot="track" class="hue-track"></div>
</hy-slider>Error state
<hy-slider label="Bitrate" max="320" value="320" error-message="Exceeds the license tier limit">
</hy-slider>Warning state
The value is accepted but worth a second look: the fill and the message take the warning color, the message is announced politely, and the form still submits. Setting warning-message raises warning; error wins when both are set.
<hy-slider label="Volume" value="92" suffix="%" warning-message="Loud: may damage hearing">
</hy-slider>Discrete slider with tick marks and min/max labels
Opt in to ticks when the slider snaps to sparse stops — without marks, step-snapping can feel like cursor lag. Add tick-labels to anchor the scale with min/max readouts below the track. tick-labels is a no-op without ticks.
<hy-slider label="Base radius" min="0" max="8" step="1" value="4" suffix="px" ticks tick-labels>
</hy-slider>Sparse, designer-named tick marks (compass directions, t-shirt sizes, …)
Use tickMarks to mark specific stops by value with optional labels. The slider keeps its step granularity for fine-grained drag; tick marks are visual references. Set tickSnap if you want the slider to restrict to those values.
const slider = document.querySelector('hy-slider');
slider.tickMarks = [
{ value: 0, label: 'T', ariaLabel: 'Top' },
{ value: 90, label: 'R', ariaLabel: 'Right' },
{ value: 180, label: 'B', ariaLabel: 'Bottom' },
{ value: 270, label: 'L', ariaLabel: 'Left' },
];<hy-slider label="Light angle" min="0" max="360" step="1"></hy-slider>Snap-to-tickMarks slider (discrete picker disguised as a slider)
const slider = document.querySelector('hy-slider');
slider.tickMarks = [
{ value: 0, label: 'S' },
{ value: 1, label: 'M' },
{ value: 2, label: 'L' },
{ value: 3, label: 'XL' },
];<hy-slider label="Size" min="0" max="3" tick-snap></hy-slider>Fractional step with auto-inferred precision
Without any precision hint, 0.357 dragged through float math can render with trailing noise like 0.357…04. By default the component rounds the displayed value to the decimal count implied by step — step="0.01" → 2 decimals.
<hy-slider label="Chroma" min="0" max="1" step="0.01" value="0.357"></hy-slider>
<!-- readout: 0.36 -->Explicit display precision
Override the auto-inferred precision — useful when step is coarser than the precision you want to show (e.g. step=1 but you want one decimal).
<hy-slider label="Zoom" step="1" value="42" display-precision="1"></hy-slider>
<!-- readout: 42.0 -->Range — two thumbs for a filter
The thumbs never cross; each is its own tab stop, named by the label plus start-label / end-label ("Price, Minimum"). With name the form submits price.start and price.end.
<hy-slider
label="Price"
range
min="0"
max="500"
step="10"
value-start="50"
value-end="250"
suffix=" CHF"
name="price"
></hy-slider>
<script>
document.querySelector('hy-slider[range]').addEventListener('change', (e) => {
console.log(e.detail.start, e.detail.end, e.detail.thumb);
});
</script>Custom formatter — percentage
When you need to reshape the number entirely (scale, locale, custom units) pass formatValue as a property binding. When set, the formatter owns the full string — the suffix prop is NOT appended.
const slider = document.querySelector('hy-slider');
slider.formatValue = (v) => `${(v * 100).toFixed(1)}%`;
slider.value = 0.357; // readout: "35.7%"Inline layout (dense parameter panels)
Label + hint sit on the left of the row, slider in the middle, value on the right — one row per parameter. Set --hy-slider-label-column-width on a wrapping element so stacked sliders align.
<style>
.parameters {
--hy-slider-label-column-width: 6rem;
}
</style>
<div class="parameters">
<hy-slider
layout="inline"
label="Hue"
hint="color angle"
min="0"
max="360"
value="208"
suffix="°"
></hy-slider>
<hy-slider
layout="inline"
label="Chroma"
hint="saturation"
min="0"
max="1"
step="0.01"
value="0.65"
></hy-slider>
<hy-slider
layout="inline"
label="Vibrancy"
hint="intensity"
min="0"
max="2"
step="0.01"
value="0.60"
></hy-slider>
</div>Form submission
<form>
<hy-slider name="brightness" min="0" max="100" value="75"></hy-slider>
<button type="submit">Save</button>
</form>Programmatic control
const slider = document.querySelector('hy-slider');
slider.stepUp(); // Advance by one step
slider.stepDown(5); // Retreat by five steps
slider.value = 42; // Set directly
slider.focus();API
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
value | value | number | 0 | The current numeric value. |
min | min | number | 0 | The minimum allowable value. |
max | max | number | 100 | The maximum allowable value. |
step | step | number | 1 | The granularity between valid values. |
disabled | disabled | boolean | false | Disables the slider, preventing user interaction. |
error | error | boolean | false | Whether the slider is in an error state. |
warning | warning | boolean | false | Warning state: the value is accepted but worth a second look. Paints the fill and the message in the warning color; never sets aria-invalid and never blocks submission. error wins when both are set. |
name | name | string | '' | The name attribute for form submission. |
label | label | string | '' | The primary label displayed above the slider. |
locale | locale | string | undefined | — | BCP-47 locale for the shown values' digits and separators. Unset, the nearest lang (across shadow roots), else the browser's language. The value and the form value stay plain numbers. |
hint | hint | string | '' | Secondary hint text displayed under the label. |
helperText | helper-text | string | '' | Helper text displayed below the slider. |
errorMessage | error-message | string | '' | Error message shown under the slider (replaces the helper text). Setting it raises error; clearing it lowers it. |
warningMessage | warning-message | string | '' | Warning message shown under the slider (replaces the helper text). Setting it raises warning; clearing it lowers it. |
suffix | suffix | string | '' | String appended to the value readout (e.g. "px", "%", "ms"). |
size | size | 'small' | 'medium' | 'large' | 'medium' | Size variant affecting track and thumb dimensions. |
layout | layout | 'block' | 'inline' | 'block' | Row layout: block (label stacked above, default) or inline (label on the left in the same row as the slider). Use inline for dense parameter panels where vertical space is at a premium. |
hideValue | hide-value | boolean | false | Hides the numeric value readout to the right of the slider. |
ticks | ticks | boolean | false | Renders a tick mark on the track at each step position. Useful for discrete sliders with sparse stops so the thumb's snapping behavior feels intentional rather than laggy. Skipped when the step count would exceed 50. |
tickLabels | tick-labels | boolean | false | Shows the min and max values as labels under the track endpoints. Requires ticks to be set — labels without tick marks would anchor the scale without communicating where the snap positions are. Ignored when tickMarks is non-empty (sparse per-tick labels are the source of truth in that mode). |
tickMarks | — | TickMark[] | [] | Sparse, designer-defined tick marks at chosen stops along the slider. Each entry has a value (positioned at ((value - min) / (max - min)) * 100% along the track), an optional label rendered below the track, and an optional ariaLabel. Out-of-range entries are silently dropped. Labels at min/max are auto-clamped (left-align at min, right-align at max) so they never bleed off the track edges. |
tickSnap | tick-snap | boolean | false | When true, restrict the slider's value to the entries declared in tickMarks, snapping to the nearest tick during drag and ignoring step for snap purposes. Defaults to false (tick marks are visual references only; drag granularity stays driven by step). |
displayPrecision | display-precision | number | undefined | — | Number of decimal places shown in the value readout, tick labels, and aria-valuetext. Leave unset to auto-infer from step (step=0.01 → 2, step=0.1 → 1, step=1 → 0). Overridden by formatValue when set. |
formatValue | — | (value: number) => string | undefined | — | Custom formatter for the value readout / tick labels / aria-valuetext. When set, owns the full display string — suffix is NOT appended and displayPrecision is ignored. Must be a function, so it cannot be set via an HTML attribute; assign it as a property (el.formatValue = fn) or via a framework property binding (.formatValue=${fn} in Lit, formatValue={fn} in React). |
range | range | boolean | false | Two thumbs on one track: the slider holds a range, value-start to value-end (price, date and size filters). The thumbs never cross — each stops at the other — and each is its own tab stop and role="slider", named by the label plus start-label / end-label. With name the form submits {name}.start and {name}.end (as hy-date-picker's range). value is ignored. |
valueStart | value-start | number | undefined | — | Range mode: the lower thumb's value. Unset starts at min. |
valueEnd | value-end | number | undefined | — | Range mode: the upper thumb's value. Unset starts at max. |
startLabel | start-label | string | 'Minimum' | Range mode: the lower thumb's name, announced after the label ("Price, Minimum"). Set it for other words ("From") or to localize. |
endLabel | end-label | string | 'Maximum' | Range mode: the upper thumb's name ("Price, Maximum"). |
inputId | input-id | string | '' | Explicit id for the underlying <input>; empty uses the fixed input (components.md "Ids"). |
autofocus | autofocus | boolean | false | Marks this slider as the auto-focus target. Read by hy-dialog (and other overlay containers) on show to choose initial focus. Also focuses on first connect when used outside an overlay, mirroring native HTML autofocus but routed through JS so SSR'd HTML doesn't pull focus during hydration. |
form | — | HTMLFormElement | null | — | Returns the form element that contains this slider, if any. |
validity | — | ValidityState | — | Returns the ValidityState object for the slider. |
validationMessage | — | string | — | Returns the validation message for the slider. |
Events
| Event | Detail | Description |
|---|---|---|
input | — | Fired during drag / keyboard adjustment. Detail: { value: number }; in range mode also start, end and thumb |
change | — | Fired when the value is committed (pointer release / after key). Detail: { value: number }; in range mode also start, end and thumb |
Slots
| Slot | Description |
|---|---|
track | Custom content inside the track, behind the fill (e.g. a gradient overlay). |
CSS Parts
| Part | Description |
|---|---|
base | The slider's outer wrapper |
label | The label container (label + hint) |
row | Row containing the slider and value readout |
input | The native range input (visually hidden, interaction-only) |
track | The background rail |
fill | The colored progress indicator |
thumb | The draggable handle (both handles in range mode) |
input-start | Range mode: the lower thumb's native input |
input-end | Range mode: the upper thumb's native input |
thumb-start | Range mode: the lower handle |
thumb-end | Range mode: the upper handle |
ticks | Container for tick marks on the track (only present when ticks) |
tick | Individual tick mark (only present when ticks) |
tick-labels | Container for min/max labels under the track (only present when both ticks and tick-labels are set, and tickMarks is empty) |
tick-marks | Container for sparse tickMarks lines overlaid on the track (only present when tickMarks has at least one in-range entry) |
tick-mark | Individual sparse tick line (vertical, cuts through the track) |
tick-mark-labels | Container for labels under the track derived from tickMarks[].label |
tick-mark-label | Individual label below a sparse tick; carries .tick-mark-label-start|center|end modifier classes for endpoint auto-clamping |
value | The numeric value readout |
helper-text | The helper text element |
error-message | The error message element |
warning-message | The warning message element |
message-icon | The icon starting the error or warning message |
CSS Custom Properties
The documented hooks. Every override hook the component reads — one per token it paints — is listed in @whitespaceux/harmony/theme-api.json, and @whitespaceux/harmony/component-bindings.json (the Harmony MCP get-bindings tool) says which part and state each one paints.
| Property | Default | Description |
|---|---|---|
--hy-slider-control-height-sm | var(--hy-control-height-sm) | Outer hit-area height for small size variant. Per-component override in the form-control height contract. Track and thumb stay smaller inside. |
--hy-slider-control-height-md | var(--hy-control-height-md) | Outer hit-area height for medium size variant. |
--hy-slider-control-height-lg | var(--hy-control-height-lg) | Outer hit-area height for large size variant. |
--hy-slider-slider-track-height-md | var(--hy-slider-track-height-md) | Track thickness (inner visual element) |
--hy-slider-slider-track-height-sm | var(--hy-slider-track-height-sm) | Track thickness (inner visual element) |
--hy-slider-slider-track-height-lg | var(--hy-slider-track-height-lg) | Track thickness (inner visual element) |
--hy-slider-ornament-pellet | var(--hy-ornament-pellet) | Thumb diameter (inner visual element) |
--hy-slider-ornament-handle | var(--hy-ornament-handle) | Thumb diameter (inner visual element) |
--hy-slider-ornament-knob | var(--hy-ornament-knob) | Thumb diameter (inner visual element) |
--hy-slider-thumb-background-surface-base | var(--hy-background-surface-base) | Thumb background color |
--hy-slider-shadow-elevation-2 | var(--hy-shadow-elevation-2) | Thumb resting shadow |
--hy-slider-shadow-elevation-3 | var(--hy-shadow-elevation-3) | Thumb hover shadow |
--hy-slider-background-surface-medium | var(--hy-background-surface-medium) | Track background color |
--hy-slider-background-brand-indicator-rest | var(--hy-background-brand-indicator-rest) | Fill (progress) color |
--hy-slider-background-danger-indicator-rest | var(--hy-background-danger-indicator-rest) | Fill (progress) color |
--hy-slider-background-warning-indicator-rest | var(--hy-background-warning-indicator-rest) | Fill (progress) color |
--hy-slider-radius-pill-md | var(--hy-radius-pill-md) | Track and thumb border radius |
--hy-slider-gap-form-sm | var(--hy-gap-form-sm) | Gap between label, slider, and helper text |
--hy-slider-gap-form-md | var(--hy-gap-form-md) | Gap between slider and value readout |
--hy-slider-value-width | — | Minimum width of the value readout column |
--hy-slider-label-column-width | — | Width of the label column in layout="inline" mode (no default — when unset, the label auto-sizes to its content). Set on a wrapping container so stacked sliders align. |
--hy-slider-foreground-default-rest | var(--hy-foreground-default-rest) | Label text color |
--hy-slider-hint-foreground-subtle-rest | var(--hy-foreground-subtle-rest) | Hint text color |
--hy-slider-value-foreground-subtle-rest | var(--hy-foreground-subtle-rest) | Value readout color |
--hy-slider-helper-foreground-subtle-rest | var(--hy-foreground-subtle-rest) | Helper text color |
--hy-slider-foreground-danger-rest | var(--hy-foreground-danger-rest) | Error message text and icon color |
--hy-slider-foreground-warning-rest | var(--hy-foreground-warning-rest) | Warning message text and icon color |
--hy-slider-gap-layout-2xs | var(--hy-gap-layout-2xs) | Gap between the message icon and its text |
--hy-slider-tick-background-surface-base | var(--hy-background-surface-base) | Color of ticks: boolean tick marks (default: surface base, so ticks cut through track + fill) |
--hy-slider-tick-stroke-md | var(--hy-stroke-md) | Width of each ticks: boolean tick mark |
--hy-slider-tick-mark-background-surface-base | var(--hy-background-surface-base) | Color of sparse tickMarks lines (the same surface color as the tick dots, so the two tick families look consistent when both are enabled) |
--hy-slider-tick-mark-label-foreground-subtle-rest | var(--hy-foreground-subtle-rest) | Text color of tickMarks labels under the track. |
--hy-slider-motion-transition-hover-duration | var(--hy-motion-transition-hover-duration) | Transition duration for hover and focus state feedback |
--hy-slider-motion-transition-hover-easing | var(--hy-motion-transition-hover-easing) | Transition easing for hover and focus state feedback |
Methods
checkValidity()
Checks if the slider's current value satisfies its constraints.
reportValidity()
Checks validity and reports any errors to the user.
focus()
Focuses the underlying range input.
blur()
Removes focus from the underlying range input.
stepUp()
Increments the value by n steps (defaults to 1). Clamps to max. Dispatches input and change events. No-op if disabled. In range mode thumb picks the thumb (default end), which stops at the other one.
stepDown()
Decrements the value by n steps (defaults to 1). Clamps to min. Dispatches input and change events. No-op if disabled. In range mode thumb picks the thumb (default end), which stops at the other one.