Sliders and Dials
A slider is a horizontal track with a control, called a thumb, that people can adjust between a minimum and maximum value.
Puppertino’s slider is a native input[type="range"] wearing macOS metrics. Keyboard, touch, form submission, and screen reader semantics come from the browser.
When to use it
Use a slider for a value on a continuous range where the rough position matters more than the exact number. Volume, brightness, playback position.
Use a stepper or a plain number field when people need a precise value. Consider pairing a slider with a field when both matter.
Anatomy
| Part | Value |
|---|---|
| Track | 4px capsule (6px on the large size), black at 8.5% when unfilled |
| Fill | --p-slider-accent, the tintable system blue (#0088FF, #0091FF in dark mode) |
| Thumb | 20 by 16px white lozenge, one soft shadow 0 0.5px 6px rgba(0,0,0,0.12) |
| Pressed | The thumb turns into a clear Liquid Glass lens, slightly larger than the thumb, and the whole track darkens by 8%. With data-p-lens="clear": flat clear glass 1.25x its size plus a 1px rim |
| Ticks | 2 by 2px dots, 4px under the track, black at 21% |
| Focus | A 3.5px accent band around the thumb |
Basic use
<input type="range" class="p-slider" min="0" max="100" value="65" />
Include the helper once per page and the accent fill follows the thumb:
<script src="src/js/sliders.js"></script>
The helper publishes the value as --p-slider-fill so the track can paint the accent portion. Firefox draws the fill natively without it. Without the helper on other browsers the track renders all gray and the slider still works, so the enhancement degrades cleanly.
Load liquid_glass.js as well and dragging turns the thumb into a faux Liquid Glass lens. The glass grows 5px wider and 4px taller than the thumb (25 by 20px for the default thumb) and stays a rounded rectangle. It is clear: the page and the track show through at their real size and in focus, and the fill ends in a rounded cap under the glass. The rim is a hairline, dark along the top and left and bright along the bottom and right. The fill color wraps it wherever the fill sits under the lens, deepest on the leading edge, so the rim is plain at the minimum, colored on its leading half mid-track, and colored all the way around at the maximum. A soft shadow falls about 6px below. While you drag, the unfilled track darkens from 8.5% to 16% and the fill darkens by 8%. The glass settles back into a solid thumb 350ms after release, and keyboard changes never lift it.
Add data-p-lens="clear" to the input for the flatter macOS 27 look. That glass grows to 1.25x the thumb plus a 1px rim (27 by 22px for the default thumb), with a ring shadow about 7px below. In light mode its rim is a gray hairline; in dark mode a dark outer line with a bright inner one, brightest along the bottom.
<script src="src/js/liquid_glass.js"></script>
Sizes
<input type="range" class="p-slider p-slider-mini" min="0" max="100" value="65" aria-label="Mini slider" />
<input type="range" class="p-slider p-slider-small" min="0" max="100" value="65" aria-label="Small slider" />
<input type="range" class="p-slider" min="0" max="100" value="65" aria-label="Regular slider" />
<input type="range" class="p-slider p-slider-large" min="0" max="100" value="65" aria-label="Large slider" />
| Class | Thumb | Track | Pressed glass |
|---|---|---|---|
.p-slider-mini | 16 × 12 | 4px | 21 × 16 |
.p-slider-small | 18 × 14 | 4px | 23 × 18 |
.p-slider | 20 × 16 | 4px | 25 × 20 |
.p-slider-large | 24 × 20 | 6px | 29 × 24 |
Narrow thumb
<input type="range" class="p-slider p-slider-narrow p-slider-ticks" style="--p-slider-ticks: 4;" min="0" max="100" step="25" value="75" aria-label="Narrow slider" />
<input type="range" class="p-slider p-slider-small p-slider-narrow" min="0" max="100" value="40" aria-label="Small narrow slider" />
.p-slider-narrow swaps the lozenge for an 8 by 16px pointer (10 by 18px on the small and large sizes). It reads as a precise position, which suits sliders with ticks.
Centered fill
<input type="range" class="p-slider p-slider-centered" min="-50" max="50" value="20" aria-label="Balance" />
<input type="range" class="p-slider p-slider-narrow p-slider-centered p-slider-origin" min="-50" max="50" value="-15" aria-label="Exposure" />
.p-slider-centered grows the fill from the middle of the track toward the thumb, for values with a neutral midpoint like balance or exposure. A 2px line marks the origin inside the track. Add .p-slider-origin for a 2 by 4px mark under the middle as well. The centered fill needs sliders.js.
Ticks
<input type="range" class="p-slider p-slider-ticks"
style="--p-slider-ticks: 5;" min="0" max="100" step="20" />
.p-slider-ticks draws 2 by 2px dots 4px under the track, one under each position the thumb can stop at. Set --p-slider-ticks to the number of intervals and match it to your step so the thumb lands on the dots. Five intervals is the default. The older style, 2px marks drawn inside the track, is still available as .p-slider-ticks-inset.
Changing the color
Add a .p-sys-* class to the input, the same tint mechanism as every other control. The fill picks up the color; the thumb and track stay neutral.
Dark mode
The unfilled track turns to white at 8.5%, the ticks to white at 21%, the thumb stays white, and the accent swaps to #0091FF. The pressed glass switches to its dark rim. No extra setup.
Accessibility
- Keyboard: arrows adjust by
step, Page Up and Down jump larger amounts, Home and End hit the extremes. All native. - Focus shows a 3.5px accent band on the thumb. Mouse clicks do not trigger it.
- Labeling: a slider has no visible label of its own. Pair it with a
.p-labelandfor/id, or setaria-label. - The 20px input height keeps the touch target comfortable around the 4px track.
Dials
<div class="p-dial" role="slider" tabindex="0" aria-label="Angle"
aria-valuemin="0" aria-valuemax="360" aria-valuenow="90"></div>
A dial is a circular slider for values that wrap around, like an angle or a hue. It is a flat plate, black at 8% (white at 7% in dark mode), with a 2px indicator that turns with the value. Pressed, the plate goes to 16%. Disabled, it drops to about 3% and the indicator to 25%. There is no shadow and no glass.
The minimum sits at 12 o’clock and the value runs clockwise through one full turn. sliders.js makes it work: drag around the center, or focus it and use the arrow keys (step from data-step, default 1), Page Up and Down, Home and End. The script keeps aria-valuenow in sync and fires input and change events on the dial. Without it, set --p-dial-angle yourself.
| Class | Plate | Indicator | Inset from edge |
|---|---|---|---|
.p-dial-mini | 16 | 2 × 4 | 2 |
.p-dial-small | 20 | 2 × 5 | 2.5 |
.p-dial | 24 | 2 × 6 | 3 |
.p-dial-large | 28 | 2 × 6 | 3 |
.p-dial-xlarge | 36 | 2 × 7 | 3 |