Slider
A filled-track input for picking a value from a continuous span.
Default
Add .slider to an <input type="range">. The filled portion paints in primary; the thumb sits as a narrow vertical pill at the value's position.
<div style="max-width: 24rem;">
<label for="basicSlider" class="form-label">Brightness</label>
<input type="range" class="slider" id="basicSlider" value="40" />
</div>Value display
Pair with <output> for a live readout. A one-line oninput handler keeps the display reactive; no framework required.
<div style="max-width: 24rem;">
<div class="d-flex justify-content-between align-items-baseline" style="margin-bottom: 0.375rem;">
<label for="outputSlider" class="form-label mb-0">Opacity</label>
<output for="outputSlider" class="text-muted-foreground">30</output>
</div>
<input type="range" class="slider" id="outputSlider" value="30"
oninput="this.parentElement.querySelector('output').value = this.value" />
</div>Min and max
Set min and max to bound the slider. Defaults to 0 through 100.
<div style="max-width: 24rem;">
<label for="minMaxSlider" class="form-label">Year</label>
<input type="range" class="slider" min="2000" max="2030" value="2026" id="minMaxSlider" />
</div>Steps
Add step to snap the thumb to fixed increments. Drop it (or use any) for fully continuous movement.
<div style="max-width: 24rem;">
<label for="stepSlider" class="form-label">Rating</label>
<input type="range" class="slider" min="0" max="5" step="0.5" value="3" id="stepSlider" />
</div>Sizes
Three sizes match the .input family. The thumb is shorter than the track so the clipped sliver at value extremes is less prominent, and every dimension scales with --st-density.
<div class="demo-stack" style="max-width: 24rem;">
<div>
<label for="smSlider" class="form-label">Small</label>
<input type="range" class="slider slider--sm" id="smSlider" value="40" />
</div>
<div>
<label for="defaultSlider" class="form-label">Default</label>
<input type="range" class="slider" id="defaultSlider" value="60" />
</div>
<div>
<label for="lgSlider" class="form-label">Large</label>
<input type="range" class="slider slider--lg" id="lgSlider" value="80" />
</div>
</div>Disabled
Add the disabled attribute to dim the track and thumb and block interaction.
<div style="max-width: 24rem;">
<label for="disabledSlider" class="form-label">Playback speed</label>
<input type="range" class="slider" id="disabledSlider" value="40" disabled />
</div>Browser validation
Native :user-invalid fires after the user moves the thumb past the allowed range. Usually a step mismatch on a constrained input. A danger border paints around the track; fill and thumb keep their brand colors.
<div style="max-width: 24rem;">
<label for="invalidSlider" class="form-label">Quantity (must land on a multiple of 10)</label>
<input type="range" class="slider" id="invalidSlider" min="0" max="100" step="10" value="35" />
</div>The seeded value sits between steps. Click the thumb then click away to trip :user-invalid; drag to a multiple of 10 to clear it.
Server validation
Set aria-invalid="true" from your form library. The attribute is sticky, so the red border persists until the library clears it.
<div style="max-width: 24rem;">
<label for="srvSlider" class="form-label">Threshold</label>
<input type="range" class="slider" id="srvSlider" aria-invalid="true" value="60" />
</div>How the fill works
No JavaScript. Two browser paths:
- Firefox exposes
::-moz-range-progress, a native pseudo whose width tracks the input's value. We paint it with--slider-fill. - Webkit / Blink doesn't expose a progress pseudo. The thumb gets a giant
box-shadow(-100vmax 0 0 100vmax var(--slider-fill)) whose right edge lands at the thumb's right edge;overflow: hiddenon the runnable track clips the shadow to the track shape. The shadow follows the thumb because the browser moves the thumb natively.
Customization
Ten variables retune .slider without touching component CSS. Override on .slider itself, on a parent scope, or on :root. The cascade scopes the change.
| Variable | Default | Use |
|---|---|---|
--slider-h |
2.25rem | Track + shell height (matches .input) |
--slider-thumb-w |
0.5rem | Thumb width (narrow vertical pill) |
--slider-thumb-h |
1.25rem | Thumb height (centered inside track) |
--slider-thumb-gap |
0.25rem | Visible space between thumb's right edge and unfilled track |
--slider-radius |
999px | Track + thumb corner radius (pill) |
--slider-track-bg |
var(--st-neutral) | Unfilled segment |
--slider-fill |
var(--st-primary) | Filled segment |
--slider-thumb-bg |
#fff | Thumb fill |
--slider-thumb-shadow |
0 1px 3px oklch(0 0 0 / 0.25) | Thumb edge definition |
--slider-ring |
var(--st-ring) | Focus halo color |
Track and thumb opt out of --st-radius. Pill is semantic. It isn't a style choice. Override --slider-radius per-component if you really want a square track.