Home / Snippets / UI Components /
Range slider
A fully styled range input with custom track and thumb — appearance: none and vendor-prefixed pseudo-elements, no JavaScript.
Quick implementation
.range-slider {
-webkit-appearance: none;
appearance: none;
width: 100%;
height: 0.35rem;
border-radius: 99rem;
background: oklch(0.3 0.02 260);
outline: none;
cursor: pointer;
}
/* Webkit track */
.range-slider::-webkit-slider-runnable-track {
height: 0.35rem;
border-radius: 99rem;
background: oklch(0.3 0.02 260);
}
/* Webkit thumb */
.range-slider::-webkit-slider-thumb {
-webkit-appearance: none;
appearance: none;
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
background: oklch(0.95 0.01 260);
border: 2px solid oklch(0.52 0.22 265);
box-shadow: 0 1px 4px oklch(0 0 0 / 0.4);
margin-top: -0.45rem;
transition: transform 0.15s, box-shadow 0.15s;
}
.range-slider:focus::-webkit-slider-thumb,
.range-slider:hover::-webkit-slider-thumb {
transform: scale(1.15);
box-shadow: 0 0 0 3px oklch(0.52 0.22 265 / 0.3);
}
/* Firefox track */
.range-slider::-moz-range-track {
height: 0.35rem;
border-radius: 99rem;
background: oklch(0.3 0.02 260);
}
/* Firefox thumb */
.range-slider::-moz-range-thumb {
width: 1.25rem;
height: 1.25rem;
border-radius: 50%;
background: oklch(0.95 0.01 260);
border: 2px solid oklch(0.52 0.22 265);
box-shadow: 0 1px 4px oklch(0 0 0 / 0.4);
cursor: pointer;
}
Prompt this to your LLM
Includes role, constraints, two framework variants, and edge cases to handle.
You are a senior frontend engineer building a CSS form component library.
Goal: A fully styled range input with custom track and thumb — no JavaScript.
Technical constraints:
- Use appearance: none (and -webkit-appearance: none) to remove all browser default styles.
- Style the track with ::-webkit-slider-runnable-track and ::-moz-range-track.
- Style the thumb with ::-webkit-slider-thumb and ::-moz-range-thumb.
- Thumb: white circle (oklch(0.95 0.01 260)), accent border (oklch(0.52 0.22 265)), drop shadow.
- Track: dark background (oklch(0.3 0.02 260)), rounded with border-radius: 99rem.
- Use oklch() for all color values, not hex or rgba().
- Add a focus/hover state on the thumb: scale(1.15) and a glowing ring using box-shadow.
Framework variant (pick one):
A) Vanilla CSS class .range-slider applicable to any <input type="range">.
B) React component — accept min, max, value, onChange, and label as props.
Edge cases to handle:
- Webkit requires margin-top on the thumb to vertically center it over the track.
- Firefox and Webkit use completely separate pseudo-elements — provide both.
- Ensure the slider is keyboard-accessible (arrow keys work natively, do not override).
Return HTML and CSS.
Why this matters in 2026
The default range input looks different across every browser and operating system — inconsistent height, colour, and thumb size make it stand out awkwardly in a polished UI. With appearance: none and vendor-prefixed pseudo-elements, you can achieve a consistent, on-brand slider in pure CSS without reaching for a JavaScript slider library.
The logic
appearance: none strips all browser default styling from the <input type="range">. Webkit (Chrome, Safari, Edge) uses ::-webkit-slider-runnable-track for the track and ::-webkit-slider-thumb for the handle; Firefox uses ::-moz-range-track and ::-moz-range-thumb. The thumb requires -webkit-appearance: none as well to fully reset it in Webkit. A negative margin-top on the Webkit thumb vertically centers it over the track because the thumb sits relative to the track height. The focus/hover state uses transform: scale(1.15) and a box-shadow ring to give clear interactive feedback.
Accessibility & performance
Range inputs are natively keyboard-accessible — arrow keys increment and decrement the value. Do not override or remove the default outline without providing an equally visible alternative. Always add an aria-label or a visible <label> associated via for/id; a range with no label has no accessible name. For live value display (e.g., showing the number next to the slider), use a linked <output> element with for pointing to the input's id — no JavaScript needed for the initial render.