API
Slider
The native range input, restyled.
Installation
pnpm dlx zazz-ui add slidernpx zazz-ui add slideryarn dlx zazz-ui add sliderbunx zazz-ui add sliderRun zazz-ui init first if the project has no zazz/ folder yet.
Examples
<input
type="range"
name="volume"
min="0"
max="100"
step="10"
value="50"
aria-label="Volume"
style="--max-w: 96"
/>/**
* slider.css — Range input (input[type="range"])
*
* @layer variables, reset
* @requires _layers.css, _variables.css
* @uses appearance: none — redraws track + thumb from theme tokens
* @tokens --ui-slider-* (@layer variables, on :root so subtree hooks work)
*
* Note: The active region is NOT filled (parity across engines); add a paint
* via JS if a filled track is needed. The thumb is centered on the track with a
* calc'd negative margin so retuning either size keeps it aligned.
*/
@layer variables {
:root {
--ui-slider-ring-color: var(--color-ring);
--ui-slider-track-h: calc(var(--spacing) * 1.5);
--ui-slider-track-bg: var(--color-muted);
--ui-slider-track-border: 1px solid var(--color-muted);
--ui-slider-track-rounded: var(--radius-full);
--ui-slider-thumb-size: calc(var(--spacing) * 4);
--ui-slider-thumb-bg: var(--color-background);
--ui-slider-thumb-shadow: var(--shadow-xs);
--ui-slider-thumb-border: 1px solid var(--color-border);
--ui-slider-thumb-rounded: var(--radius-full);
}
}
@layer reset {
:where(input[type="range"]) {
/* derived on the element so a subtree --ui-slider-track-h keeps the thumb centered */
--ui-slider-thumb-offset: calc(var(--ui-slider-track-h) * -1);
-webkit-appearance: none;
appearance: none;
inline-size: 100%;
background: transparent;
cursor: grab;
border-radius: var(--ui-slider-track-rounded);
/* transparent outline keeps focus visible in forced-colors / high-contrast
modes; the visible focus ring lives on the thumb via --shadow-ring */
outline: var(--outline-width) var(--outline-style) transparent;
outline-offset: var(--outline-offset);
}
:where(input[type="range"])::-webkit-slider-runnable-track {
block-size: var(--ui-slider-track-h);
inline-size: 100%;
background: var(--ui-slider-track-bg);
border: var(--ui-slider-track-border);
border-radius: var(--ui-slider-track-rounded);
}
:where(input[type="range"])::-moz-range-track {
block-size: var(--ui-slider-track-h);
inline-size: 100%;
background: var(--ui-slider-track-bg);
border: var(--ui-slider-track-border);
border-radius: var(--ui-slider-track-rounded);
}
:where(input[type="range"])::-webkit-slider-thumb {
/* the ring is a box-shadow so it composes with --ui-slider-thumb-shadow;
widths animate from 0 so it grows in on focus */
--_ring-offset-width: 0px;
--_ring-width: 0px;
--_ring-fill: color-mix(in oklch, var(--ui-slider-ring-color) var(--ring-opacity), transparent);
box-shadow:
0 0 0 var(--_ring-offset-width) var(--ring-offset-shadow-color),
0 0 0 calc(var(--_ring-offset-width) + var(--_ring-width))
var(--_ring-fill, var(--ring-shadow-color)),
var(--ui-slider-thumb-shadow);
-webkit-appearance: none;
appearance: none;
margin-block-start: var(--ui-slider-thumb-offset);
inline-size: var(--ui-slider-thumb-size);
block-size: var(--ui-slider-thumb-size);
background-color: var(--ui-slider-thumb-bg);
border: var(--ui-slider-thumb-border);
border-radius: var(--ui-slider-thumb-rounded);
transition: var(--default-transition);
}
:where(input[type="range"])::-moz-range-thumb {
/* the ring is a box-shadow so it composes with --ui-slider-thumb-shadow;
widths animate from 0 so it grows in on focus */
--_ring-offset-width: 0px;
--_ring-width: 0px;
--_ring-fill: color-mix(in oklch, var(--ui-slider-ring-color) var(--ring-opacity), transparent);
appearance: none;
inline-size: var(--ui-slider-thumb-size);
block-size: var(--ui-slider-thumb-size);
background-color: var(--ui-slider-thumb-bg);
border: var(--ui-slider-thumb-border);
border-radius: var(--ui-slider-thumb-rounded);
box-shadow:
0 0 0 var(--_ring-offset-width) var(--ring-offset-shadow-color),
0 0 0 calc(var(--_ring-offset-width) + var(--_ring-width))
var(--_ring-fill, var(--ring-shadow-color)),
var(--ui-slider-thumb-shadow);
transition: var(--default-transition);
}
@media (hover: hover) {
:where(input[type="range"]:hover) {
--ui-slider-thumb-border: 1px solid var(--color-primary);
}
}
:where(input[type="range"]:active) {
--ui-slider-thumb-shadow: var(--shadow-xs), var(--shadow-ring);
cursor: grabbing;
}
:where(input[type="range"]:focus-visible) {
--ui-slider-thumb-shadow: var(--shadow-xs), var(--shadow-ring);
}
:where(input[type="range"]:disabled) {
opacity: 0.5;
cursor: not-allowed;
}
}Attributes
This primitive is a composition of other primitives and utilities: it has no presets, slots, or hooks of its own. Copy its example and adapt it.
Hooks
Set a hook on :root, on a subtree, or inline on one element. A state hook (--…--hover) themes that state only; the primitive keeps its other states.
| Hook | Default | State |
|---|---|---|
--ui-slider-ring-color | var(--color-ring) | — |
--ui-slider-thumb-bg | var(--color-background) | — |
--ui-slider-thumb-border | 1px solid var(--color-border) | — |
--ui-slider-thumb-offset | calc(var(--ui-slider-track-h) * -1) | — |
--ui-slider-thumb-rounded | var(--radius-full) | — |
--ui-slider-thumb-shadow | var(--shadow-xs) | — |
--ui-slider-thumb-size | calc(var(--spacing) * 4) | — |
--ui-slider-track-bg | var(--color-muted) | — |
--ui-slider-track-border | 1px solid var(--color-muted) | — |
--ui-slider-track-h | calc(var(--spacing) * 1.5) | — |
--ui-slider-track-rounded | var(--radius-full) | — |