Slider labels and track gaps align with native handles after resizing, including RTL and vertical layouts. Paired inputs accept track clicks and stay in order. In forced-colors mode, tracks use system colors.
The native range inputs handle keyboard and pointer interaction. Vertical values increase from bottom to top. For precise entry, pair a labeled number input with a range input. To update the slider, assign the range input's value and dispatch an input event. Use matching bounds and steps on paired ranges.
Sliders let people pick from a range of values. An
<input type="range"> is the control. A wrapping
.slider or <label> is the host. The plugin is
Expressive.Slider.
A slider comes in three variants. In standard, the active track fills from the start. In centered, it grows from the midpoint. Range has two handles, with the active track between them. A slider can be horizontal or vertical, and comes in five sizes: XS (the default), S, M, L, and XL. An inset icon, discrete stops, and a value indicator are optional.
Tokens follow the
M3 slider spec
(Expressive). The handle is a 4dp stop with a 6dp gap to each
track. The active track is primary and the inactive
track is secondary-container. The end of an inactive track
has a 4dp stop. The value label is a 40dp
inverse-surface bubble.
Slider is not in AutoInit(). Importing the IIFE
bundle calls Expressive.Slider.Init(). That call
starts every input[type=range] already in the
document and keeps the active track in sync.
<label>
Volume
<input type="range" min="0" max="100" value="40">
</label>
Variants
Standard is the default. centered grows the active
track from 50%. A range slider is two inputs in one
.slider host.
<div class="slider">
<input type="range" min="0" max="100" value="40" aria-label="Standard">
</div>
<div class="slider centered">
<input type="range" min="0" max="100" value="30" aria-label="Centered">
</div>
<div class="slider">
<input type="range" min="0" max="100" value="25" aria-label="Range start">
<input type="range" min="0" max="100" value="75" aria-label="Range end">
</div>
Sizes
Set the size class on the host. The default is XS (16dp track, 44dp
handle). s / small is S,
m / medium is M,
l / large is L, and
xl is XL.
<div class="slider s">…</div>
<div class="slider m">…</div>
<div class="slider l">…</div>
<div class="slider xl">…</div>
Inset icon, stops, value
On M, L, and XL, a leading icon sits in the active track. Those
sizes are tall enough to hold it. Add stops and a
numeric step to paint a dot on the active track at
each step from min, except under the icon. Without a
step, or with step="any", the track has
no dots. Dragging shows the value in a 40dp bubble.
<div class="slider m">
<span class="material-symbols" aria-hidden="true">volume_up</span>
<input type="range" min="0" max="100" value="55" aria-label="Volume">
</div>
<div class="slider stops">
<input type="range" min="0" max="100" step="20" value="40" aria-label="Stops">
</div>
Vertical
vertical turns the track upright, with the minimum at
the bottom.
<div class="slider vertical m">
<input type="range" min="0" max="100" value="60" aria-label="Level">
</div>
Disabled
The native disabled attribute drops the control to 38%.
Initialization
Put the script after the inputs, or call init
yourself. There are no options. For a slider added after load:
document.addEventListener('DOMContentLoaded', function() {
const elems = document.querySelectorAll('input[type=range]');
Expressive.Slider.init(elems);
});
Expressive.Slider.init(document.querySelector('#volume'));
Methods
const instance = Expressive.Slider.getInstance(elem);
instance.destroy();
destroy() removes the plugin instance and the value label, tears down its event handlers, and restores the inline custom properties it changed.