Slider
Sliders let people select a value from a range by dragging a handle along a track. Use one when the position in 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 browser. The component handles pointer
input itself, so a range slider grabs the nearer of its two handles.
Usage
value is bindable and defaults to 0. The range goes from min to max (0 to 100 by default).
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="stack">
<Slider value={40} aria-label="Volume" />
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
</style>
Variants
There are three variants. All use the same handle and gaps:
- Standard fills from the start of the track to the handle.
- Centered fills from the midpoint outward, so the handle shows an offset from neutral. Use it for balance, EQ or exposure correction.
- Range has two handles and fills between them. Bind
valueandendValue. The handles cannot cross.
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="stack">
<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' }}
/>
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
</style>
Sizes
Five sizes, from xs to xl. The size sets the track height, the handle
height and the track corner radius. Bigger tracks get proportionally less rounding, so an xl slider looks like a rounded rectangle, not a pill. The default is xs.
<script lang="ts">
import { Slider } from 'noph-ui'
const sizes: ('xs' | 's' | 'm' | 'l' | 'xl')[] = ['xs', 's', 'm', 'l', 'xl']
</script>
<div class="stack wide">
{#each sizes as size (size)}
<Slider {size} value={50} aria-label="Size {size}" />
{/each}
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
.wide {
gap: 2.5rem;
}
</style>
| Size | Track height | Handle height | Track corner | Icon size |
|---|---|---|---|---|
xs | 16dp | 44dp | 8dp | n/a |
s | 24dp | 44dp | 8dp | n/a |
m | 40dp | 44dp | 12dp | 24dp |
l | 56dp | 68dp | 16dp | 24dp |
xl | 96dp | 108dp | 28dp | 32dp |
Orientation
orientation="vertical" runs the track bottom to top. The length comes from --np-slider-length (12rem by default). The native input uses the same
writing mode, so ↑/↓ move the handle up and down.
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="row">
<Slider orientation="vertical" value={60} aria-label="Brightness" />
<Slider orientation="vertical" size="m" value={60} aria-label="Brightness" />
<Slider
orientation="vertical"
size="l"
--np-slider-length="9rem"
value={60}
aria-label="Brightness"
/>
</div>
<style>
.row {
display: flex;
align-items: flex-end;
gap: 2.5rem;
}
</style>
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 hidden, so the gap around the handle stays clear. At the two
ends, the track's own stop indicators replace the ticks.
A discrete handle moves between the two track corners, not along the full track, so it lines up with the tick it selects.
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="stack">
<Slider step={25} ticks value={50} min={0} max={100} aria-label="Rating" />
<Slider step={10} ticks size="m" value={40} aria-label="Volume in tens" />
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
</style>
Value indicator
labeled shows a value indicator above the handle while it is pressed, dragged or has
keyboard focus. The container grows with its content, so format can return text of any
length.
A continuous slider reports the exact position it was dragged to, which is rarely a good number to
show. Pair labeled with a step, or round in format.
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="stack with-label">
<Slider labeled step={1} value={40} aria-label="Volume" />
<Slider labeled size="m" value={60} format={(v) => `${Math.round(v)}%`} aria-label="Brightness" />
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
.with-label {
gap: 3rem;
}
</style>
Inset icon
The icon snippet is drawn inside the leading end of the active track. When the active
track gets too short for it, at a low value or between the handles of a range slider,
the icon moves to the inactive track and takes the inactive track's colour. The icon needs room,
so use it with m and larger.
You render the snippet, so a slider at zero can show a different icon. Drag the volume slider
below to the start and the icon changes to volume_off.
<script lang="ts">
import { Icon } from 'noph-ui/icons'
import { Slider } from 'noph-ui'
let volume = $state(40)
</script>
<div class="stack">
<Slider size="m" bind:value={volume} aria-label="Volume">
{#snippet icon()}
<Icon>{volume === 0 ? 'volume_off' : 'volume_up'}</Icon>
{/snippet}
</Slider>
<Slider size="xl" value={60} aria-label="Brightness">
{#snippet icon()}
<Icon>light_mode</Icon>
{/snippet}
</Slider>
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
</style>
Disabled
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="stack">
<Slider disabled value={40} aria-label="Disabled" />
<Slider disabled range value={20} endValue={70} aria-label="Disabled range" />
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
</style>
Right-to-left
Horizontal sliders mirror in a right-to-left context, following the inherited dir.
The track fills from the right and the stop indicator moves to the left edge.
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="stack" dir="rtl">
<Slider value={60} aria-label="Volume, right to left" />
<Slider range value={30} endValue={70} aria-label="Price, right to left" />
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
</style>
Theming
| Token | Default 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-height | per size, 1rem at xs |
--np-slider-track-shape | per size, 0.5rem at xs |
--np-slider-track-inside-shape | 0.125rem |
--np-slider-handle-height | per size, 2.75rem at xs |
--np-slider-handle-width | 0.25rem |
--np-slider-handle-shape | --np-shape-corner-full |
--np-slider-stop-indicator-size | 0.25rem |
--np-slider-icon-size | per size, 1.5rem at m |
--np-slider-length | 12rem, vertical only |
--np-slider-icon-color | --np-color-on-primary |
--np-slider-icon-inactive-color | --np-color-on-secondary-container |
--np-slider-icon-padding | 0.625rem |
--np-slider-handle-width-focus | 0.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. Its
height stays the same. The disabled colours get their own opacity, so set a full colour, not a faded
one.
Example
<script lang="ts">
import { Slider } from 'noph-ui'
</script>
<div class="stack">
<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}
aria-label="Slider custom"
/>
</div>
<style>
.stack {
display: flex;
flex-direction: column;
gap: 2rem;
min-width: min(22rem, 70vw);
}
</style>
Accessibility
The slider has no text, so pass an aria-label (or aria-labelledby) that
says what it controls. A range slider has two inputs. The spread attributes go to the
start handle, and endInputAttributes go to 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 steps. This comes from the native input.
A format function also sets aria-valuetext, so a screen reader says 21 percent instead of 21. Use it when the number needs a unit.
API
Attributes
Anything not listed here is forwarded to the underlying <input type="range">.
| Attribute | Type | Default | Description |
|---|---|---|---|
min | number | 0 | Lowest selectable value. |
max | number | 100 | Highest selectable value. |
step | number | 0 | Step 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. |
range | boolean | false | Two handles selecting a sub-range. Bind value and endValue. |
centered | boolean | false | Fills the active track from the midpoint instead of the start. |
labeled | boolean | false | Shows the value indicator on press, drag and keyboard focus. |
ticks | boolean | false | Draws a stop indicator on every step. Requires step. |
disabled | boolean | false | Disables the slider. |
format | (value: number) => string | String | Formats the value indicator. |
icon | Snippet | undefined | Icon drawn inside the leading end of the active track. |
endInputAttributes | HTMLInputAttributes | undefined | Attributes for the end handle's input. range only. |
Bindables
| Attribute | Type | Description |
|---|---|---|
value | number | Current value. With range, the start handle's value. |
endValue | number | undefined | End handle's value. range only. Defaults to max. |
element | HTMLDivElement | The slider's root element. |
inputElement | HTMLInputElement | The start handle's input. |
endInputElement | HTMLInputElement | The end handle's input. range only. |