Slider

Sliders let people select a value from a range by dragging a handle along a track. Reach for one when the position within the range matters more than the exact number, as with volume, brightness or a price filter.

The component wraps a native <input type="range">, so keyboard support, form participation and the slider role come from the platform. Pointer input is handled by the component itself, which is what lets a range slider grab the nearer of its two handles.

Usage

value is bindable and defaults to 0; the range is min/max (0 to 100 unless you say otherwise).

<Slider value={40} aria-label="Volume" />

Variants

Three ways to read the track, all sharing the same handle and gap anatomy:

  • Standard fills from the start of the track to the handle.
  • Centered fills from the midpoint outward, so the handle reads as an offset from neutral. Good for balance, EQ or an exposure correction.
  • Range has two handles and fills between them. Bind value and endValue; the handles cannot cross.
<Slider value={40} aria-label="Standard" />
<Slider centered value={20} aria-label="Centered" />
<Slider
	range
	value={30}
	endValue={70}
	aria-label="Minimum price"
	endInputAttributes={{ 'aria-label': 'Maximum price' }}
/>

Sizes

Five sizes, from xs to xl. The size sets the track height, the handle height and the track corner radius together. Bigger tracks get proportionally less rounding, which is why an xl slider reads as a rounded rectangle rather than a pill. The default is xs.

<Slider size="xs" value={50} />
<Slider size="s" value={50} />
<Slider size="m" value={50} />
<Slider size="l" value={50} />
<Slider size="xl" value={50} />
SizeTrack heightHandle heightTrack cornerIcon size
xs16dp44dp8dpn/a
s24dp44dp8dpn/a
m40dp44dp12dp24dp
l56dp68dp16dp24dp
xl96dp108dp28dp32dp

Orientation

orientation="vertical" runs the track bottom to top. The length comes from --np-slider-length (12rem by default), and the native input follows the same writing mode, so / keep working the way you would expect.

<Slider orientation="vertical" value={60} aria-label="Brightness" />
<Slider orientation="vertical" size="l" --np-slider-length="9rem" value={60} />

Steps and stop indicators

Set step to make the slider discrete. Add ticks to draw a stop indicator on every step; the tick under the handle is omitted so the gap around the handle stays clean, and the ticks at the two ends give way to the track's own stop indicators.

A discrete handle travels between the two track corners rather than the full track, which is what keeps it aligned with the tick it selects.

<Slider step={25} ticks value={50} aria-label="Rating" />
<Slider step={10} ticks size="m" value={40} aria-label="Volume in tens" />

Value indicator

labeled shows a value indicator above the handle on hover, focus and drag. The container grows with its content, so format is free to return whatever reads best.

A continuous slider reports the exact position it was dragged to, which is rarely something you want to put in front of someone. Pair labeled with a step, or round in format.

<Slider labeled step={1} value={40} aria-label="Volume" />
<Slider labeled size="m" value={60} format={(v) => `${Math.round(v)}%`} aria-label="Brightness" />

Inset icon

The icon snippet is drawn inside the leading end of the active track. Once the active track runs out of room for it, which happens at a low value or between the handles of a range slider, the icon moves over to the inactive track instead of sitting there half clipped, and takes the inactive track's colour with it. It needs room either way, so it is meant for m and larger.

The snippet is yours to render, so a slider at zero can show a different icon: drag the volume slider below to the start and the icon becomes volume_off.

<Slider size="m" bind:value={volume} aria-label="Volume">
	{#snippet icon()}
		<Icon>{volume === 0 ? 'volume_off' : 'volume_up'}</Icon>
	{/snippet}
</Slider>

Disabled

<Slider disabled value={40} aria-label="Disabled" />

Right-to-left

Horizontal sliders mirror themselves in a right-to-left context. The track fills from the right and the stop indicator moves to the left edge. There is nothing to configure, since it follows the inherited dir.

<div dir="rtl">
	<Slider value={60} />
</div>

Theming

TokenDefault value
--np-slider-active-track-color--np-color-primary
--np-slider-inactive-track-color--np-color-secondary-container
--np-slider-handle-color--np-color-primary
--np-slider-active-stop-color--np-color-on-primary
--np-slider-inactive-stop-color--np-color-on-secondary-container
--np-slider-label-container-color--np-color-inverse-surface
--np-slider-label-text-color--np-color-inverse-on-surface
--np-slider-track-heightper size, 1rem at xs
--np-slider-track-shapeper size, 0.5rem at xs
--np-slider-track-inside-shape0.125rem
--np-slider-handle-heightper size, 2.75rem at xs
--np-slider-handle-width0.25rem
--np-slider-handle-shape--np-shape-corner-full
--np-slider-stop-indicator-size0.25rem
--np-slider-icon-sizeper size, 1.5rem at m
--np-slider-length12rem, vertical only
--np-slider-icon-color--np-color-on-primary
--np-slider-icon-inactive-color--np-color-on-secondary-container
--np-slider-icon-padding0.625rem
--np-slider-handle-width-focus0.125rem
--np-slider-disabled-active-track-color--np-color-on-surface
--np-slider-disabled-inactive-track-color--np-color-on-surface
--np-slider-disabled-handle-color--np-color-on-surface

The handle narrows to --np-slider-handle-width-focus while it is pressed or focused, and keeps its full height either way. The disabled colours are blended with an opacity of their own, so they take a colour, not a pre-faded one.

Example

<Slider
	--np-slider-active-track-color="var(--np-color-error)"
	--np-slider-inactive-track-color="var(--np-color-error-container)"
	--np-slider-handle-color="var(--np-color-error)"
	--np-slider-track-shape="0.25rem"
	value={40}
/>

Accessibility

The slider has no text of its own, so pass an aria-label (or aria-labelledby) describing what it controls. A range slider has two inputs: the spread attributes go to the start handle, while endInputAttributes covers the end handle. Label both.

Arrow keys move by one step, Home/End jump to the ends, and Page Up/Page Down move in larger increments. All of that comes from the native input.

A format function feeds aria-valuetext, so a screen reader announces 21 percent rather than the bare 21. Reach for it whenever the number needs a unit to make sense.

API

Attributes

Anything not listed here is forwarded to the underlying <input type="range">.

AttributeTypeDefaultDescription
minnumber0Lowest selectable value.
maxnumber100Highest selectable value.
stepnumber0Step increment. 0 makes the slider continuous.
size'xs' | 's' | 'm' | 'l' | 'xl''xs'Track height, handle height and corner radius.
orientation'horizontal' | 'vertical''horizontal'Vertical sliders run bottom to top.
rangebooleanfalseTwo handles selecting a sub-range. Bind value and endValue.
centeredbooleanfalseGrows the active track out of the midpoint instead of the start.
labeledbooleanfalseShows the value indicator on hover, focus and drag.
ticksbooleanfalseDraws a stop indicator on every step. Requires step.
disabledbooleanfalseDisables the slider.
format(value: number) => stringStringFormats the value indicator.
iconSnippetundefinedIcon drawn inside the leading end of the active track.
endInputAttributesHTMLInputAttributesundefinedAttributes for the end handle's input. range only.

Bindables

AttributeTypeDescription
valuenumberCurrent value. With range, the start handle's value.
endValuenumber | undefinedEnd handle's value. range only; defaults to max.
elementHTMLDivElementThe slider's root element.
inputElementHTMLInputElementThe start handle's input.
endInputElementHTMLInputElementThe end handle's input. range only.