/* Vertical range sliders: writing-mode:vertical-lr turns a real <input type="range"> upright and direction:rtl
puts the minimum at the bottom. Arrow keys, Page Up/Down, Home and End work as on any range input.
--p (0 to 1) is set by the script below. --vt-along is the thumb size along the track, --vt-cross across it. */
.vt{--vt-accent:#c2410c;--vt-track:#8a8178;--vt-along:20px;--vt-cross:20px;--vt-w:6px;--vt-len:170px;--p:0;
display:flex;flex-direction:column;align-items:center;gap:10px;min-width:0}
.vt output{font-size:.85rem;font-weight:700;color:var(--vt-accent);font-variant-numeric:tabular-nums;white-space:nowrap}
.vt label{font-size:.8rem;font-weight:600;color:#44403c;white-space:nowrap}
.vt input[type="range"]{-webkit-appearance:none;appearance:none;writing-mode:vertical-lr;direction:rtl;
width:var(--vt-cross);height:var(--vt-len);margin:0;background:none;cursor:pointer}
.vt input[type="range"]:focus-visible{outline:none}
.vt input[type="range"]::-webkit-slider-runnable-track{width:var(--vt-w);border-radius:999px;
background:linear-gradient(to top,var(--vt-accent) 0 calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p)),var(--vt-track) 0)}
.vt input[type="range"]::-moz-range-track{width:var(--vt-w);border-radius:999px;
background:linear-gradient(to top,var(--vt-accent) 0 calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p)),var(--vt-track) 0)}
.vt input[type="range"]::-webkit-slider-thumb{-webkit-appearance:none;appearance:none;box-sizing:border-box;
width:var(--vt-cross);height:var(--vt-along);margin-left:calc((var(--vt-w) - var(--vt-cross)) / 2);
border:2px solid var(--vt-accent);border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(28,25,23,.3)}
.vt input[type="range"]::-moz-range-thumb{box-sizing:border-box;width:var(--vt-cross);height:var(--vt-along);
border:2px solid var(--vt-accent);border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(28,25,23,.3)}
.vt input[type="range"]:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--vt-accent)}
.vt input[type="range"]:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--vt-accent)}
.vt-bank{display:flex;justify-content:space-around;gap:6px}
/* equaliser: the fill grows from the 0 dB centre towards the thumb; a short line marks 0 dB */
.vt.eq input[type="range"]{background:linear-gradient(#8a8178,#8a8178) center / 100% 2px no-repeat}
.vt.eq input[type="range"]::-webkit-slider-runnable-track{background:linear-gradient(to top,var(--vt-track) 0 min(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),
var(--vt-accent) 0 max(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),var(--vt-track) 0)}
.vt.eq input[type="range"]::-moz-range-track{background:linear-gradient(to top,var(--vt-track) 0 min(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),
var(--vt-accent) 0 max(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),var(--vt-track) 0)}
/* mixer fader: a slim dark slot, a flat cap with a grip line, and dB ticks behind */
.vt.fader{--vt-along:16px;--vt-cross:34px;--vt-w:4px;--vt-len:190px;--vt-track:#57534e}
.vt.fader output{color:#1c1917}
.vt.fader input[type="range"]{background:linear-gradient(to top,#a8a29e 0 1px,transparent 0) 0 7.5px / 100% calc((100% - 16px) / 8) repeat-y}
.vt.fader input[type="range"]::-webkit-slider-runnable-track{background:var(--vt-track)}
.vt.fader input[type="range"]::-moz-range-track{background:var(--vt-track)}
.vt.fader input[type="range"]::-webkit-slider-thumb{border:1px solid #57534e;border-radius:4px;
background:linear-gradient(#1c1917,#1c1917) center / 100% 2px no-repeat,linear-gradient(#fafaf9,#d6d3d1);box-shadow:0 2px 4px rgba(28,25,23,.35)}
.vt.fader input[type="range"]::-moz-range-thumb{border:1px solid #57534e;border-radius:4px;
background:linear-gradient(#1c1917,#1c1917) center / 100% 2px no-repeat,linear-gradient(#fafaf9,#d6d3d1);box-shadow:0 2px 4px rgba(28,25,23,.35)}
/* tall pill: the whole track is the control, icon at the foot, value at the head */
.vt.pill{--vt-along:12px;--vt-cross:64px;--vt-w:64px;--vt-len:180px;--vt-track:#e7e2da}
.vt-pill{position:relative}
.vt.pill input[type="range"]{border-radius:22px}
.vt.pill input[type="range"]::-webkit-slider-runnable-track{border-radius:22px;box-shadow:inset 0 0 0 1px #8a8178}
.vt.pill input[type="range"]::-moz-range-track{border-radius:22px;box-shadow:inset 0 0 0 1px #8a8178}
.vt.pill input[type="range"]::-webkit-slider-thumb{margin-left:0;border:0;border-radius:0;box-shadow:none;background:linear-gradient(#fff,#fff) center / 24px 4px no-repeat}
.vt.pill input[type="range"]::-moz-range-thumb{border:0;border-radius:0;box-shadow:none;background:linear-gradient(#fff,#fff) center / 24px 4px no-repeat}
.vt.pill input[type="range"]:focus-visible{outline:2px solid var(--vt-accent);outline-offset:3px}
.vt.pill input[type="range"]:focus-visible::-webkit-slider-thumb{box-shadow:none}
.vt.pill input[type="range"]:focus-visible::-moz-range-thumb{box-shadow:none}
.vt-pill svg{position:absolute;left:50%;bottom:16px;width:24px;height:24px;transform:translateX(-50%);color:#fff;pointer-events:none}
.vt.pill label{font-size:.9rem;color:#1c1917}
.vt.pill output{color:#1c1917;font-size:.95rem}
JavaScript
// Keeps each slider's filled track (--p) and its <output> in step with the value.
document.querySelectorAll('.vt input[type="range"]').forEach(function (range) {
var field = range.closest('.vt');
var out = field.querySelector('output');
function sync() {
var v = Number(range.value);
var sign = range.hasAttribute('data-signed') ? (v > 0 ? '+' : v < 0 ? '−' : '') : (v < 0 ? '−' : '');
var text = sign + Math.abs(v).toLocaleString('en-US') + (range.dataset.suffix || '');
field.style.setProperty('--p', (v - range.min) / (range.max - range.min));
if (out) out.textContent = text;
range.setAttribute('aria-valuetext', text);
}
range.addEventListener('input', sync);
sync();
});
/* Vertical range sliders: writing-mode:vertical-lr turns a Bootstrap .form-range upright and direction:rtl
puts the minimum at the bottom. Arrow keys, Page Up/Down, Home and End work as on any range input.
--p (0 to 1) is set by the script below. --vt-along is the thumb size along the track, --vt-cross across it. */
.vt{--vt-accent:#c2410c;--vt-track:#8a8178;--vt-along:20px;--vt-cross:20px;--vt-w:6px;--vt-len:170px;--p:0;
gap:10px;min-width:0}
.vt output{font-size:.85rem;color:var(--vt-accent);font-variant-numeric:tabular-nums;white-space:nowrap}
.vt .form-label{font-size:.8rem;color:#44403c;white-space:nowrap}
.vt .form-range{writing-mode:vertical-lr;direction:rtl;width:var(--vt-cross);height:var(--vt-len)}
.vt .form-range::-webkit-slider-runnable-track{width:var(--vt-w);height:100%;border-radius:999px;
background:linear-gradient(to top,var(--vt-accent) 0 calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p)),var(--vt-track) 0)}
.vt .form-range::-moz-range-track{width:var(--vt-w);height:100%;border-radius:999px;
background:linear-gradient(to top,var(--vt-accent) 0 calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p)),var(--vt-track) 0)}
.vt .form-range::-webkit-slider-thumb{box-sizing:border-box;
width:var(--vt-cross);height:var(--vt-along);margin-left:calc((var(--vt-w) - var(--vt-cross)) / 2);
border:2px solid var(--vt-accent);border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(28,25,23,.3)}
.vt .form-range::-moz-range-thumb{box-sizing:border-box;width:var(--vt-cross);height:var(--vt-along);
border:2px solid var(--vt-accent);border-radius:50%;background:#fff;box-shadow:0 1px 3px rgba(28,25,23,.3)}
.vt .form-range:focus-visible::-webkit-slider-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--vt-accent)}
.vt .form-range:focus-visible::-moz-range-thumb{box-shadow:0 0 0 3px #fff,0 0 0 5px var(--vt-accent)}
/* equaliser: the fill grows from the 0 dB centre towards the thumb; a short line marks 0 dB */
.vt.eq .form-range{background:linear-gradient(#8a8178,#8a8178) center / 100% 2px no-repeat}
.vt.eq .form-range::-webkit-slider-runnable-track{background:linear-gradient(to top,var(--vt-track) 0 min(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),
var(--vt-accent) 0 max(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),var(--vt-track) 0)}
.vt.eq .form-range::-moz-range-track{background:linear-gradient(to top,var(--vt-track) 0 min(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),
var(--vt-accent) 0 max(50%,calc(var(--vt-along) / 2 + (100% - var(--vt-along)) * var(--p))),var(--vt-track) 0)}
/* mixer fader: a slim dark slot, a flat cap with a grip line, and dB ticks behind */
.vt.fader{--vt-along:16px;--vt-cross:34px;--vt-w:4px;--vt-len:190px;--vt-track:#57534e}
.vt.fader output{color:#1c1917}
.vt.fader .form-range{background:linear-gradient(to top,#a8a29e 0 1px,transparent 0) 0 7.5px / 100% calc((100% - 16px) / 8) repeat-y}
.vt.fader .form-range::-webkit-slider-runnable-track{background:var(--vt-track)}
.vt.fader .form-range::-moz-range-track{background:var(--vt-track)}
.vt.fader .form-range::-webkit-slider-thumb{border:1px solid #57534e;border-radius:4px;
background:linear-gradient(#1c1917,#1c1917) center / 100% 2px no-repeat,linear-gradient(#fafaf9,#d6d3d1);box-shadow:0 2px 4px rgba(28,25,23,.35)}
.vt.fader .form-range::-moz-range-thumb{border:1px solid #57534e;border-radius:4px;
background:linear-gradient(#1c1917,#1c1917) center / 100% 2px no-repeat,linear-gradient(#fafaf9,#d6d3d1);box-shadow:0 2px 4px rgba(28,25,23,.35)}
/* tall pill: the whole track is the control, icon at the foot, value at the head */
.vt.pill{--vt-along:12px;--vt-cross:64px;--vt-w:64px;--vt-len:180px;--vt-track:#e7e2da}
.vt-pill{position:relative}
.vt.pill .form-range{border-radius:22px}
.vt.pill .form-range::-webkit-slider-runnable-track{border-radius:22px;box-shadow:inset 0 0 0 1px #8a8178}
.vt.pill .form-range::-moz-range-track{border-radius:22px;box-shadow:inset 0 0 0 1px #8a8178}
.vt.pill .form-range::-webkit-slider-thumb{margin-left:0;border:0;border-radius:0;box-shadow:none;background:linear-gradient(#fff,#fff) center / 24px 4px no-repeat}
.vt.pill .form-range::-moz-range-thumb{border:0;border-radius:0;box-shadow:none;background:linear-gradient(#fff,#fff) center / 24px 4px no-repeat}
.vt.pill .form-range:focus-visible{outline:2px solid var(--vt-accent);outline-offset:3px}
.vt.pill .form-range:focus-visible::-webkit-slider-thumb{box-shadow:none}
.vt.pill .form-range:focus-visible::-moz-range-thumb{box-shadow:none}
.vt-pill svg{position:absolute;left:50%;bottom:16px;width:24px;height:24px;transform:translateX(-50%);color:#fff;pointer-events:none}
.vt.pill .form-label{font-size:.9rem;color:#1c1917}
.vt.pill output{color:#1c1917;font-size:.95rem}
JavaScript
// Keeps each slider's filled track (--p) and its <output> in step with the value.
document.querySelectorAll('.vt .form-range').forEach(function (range) {
var field = range.closest('.vt');
var out = field.querySelector('output');
function sync() {
var v = Number(range.value);
var sign = range.hasAttribute('data-signed') ? (v > 0 ? '+' : v < 0 ? '−' : '') : (v < 0 ? '−' : '');
var text = sign + Math.abs(v).toLocaleString('en-US') + (range.dataset.suffix || '');
field.style.setProperty('--p', (v - range.min) / (range.max - range.min));
if (out) out.textContent = text;
range.setAttribute('aria-valuetext', text);
}
range.addEventListener('input', sync);
sync();
});
// Keeps each slider's filled track (--p) and its <output> in step with the value.
document.querySelectorAll('.vt input[type="range"]').forEach(function (range) {
var field = range.closest('.vt');
var out = field.querySelector('output');
function sync() {
var v = Number(range.value);
var sign = range.hasAttribute('data-signed') ? (v > 0 ? '+' : v < 0 ? '−' : '') : (v < 0 ? '−' : '');
var text = sign + Math.abs(v).toLocaleString('en-US') + (range.dataset.suffix || '');
field.style.setProperty('--p', (v - range.min) / (range.max - range.min));
if (out) out.textContent = text;
range.setAttribute('aria-valuetext', text);
}
range.addEventListener('input', sync);
sync();
});
About this set
Upright sliders for audio and hardware style interfaces. Five-band equaliser runs five sliders from minus 12 to plus 12 dB; the fill grows from a centre mark at 0 dB up or down to the thumb, so boosts and cuts read at a glance. Mixer faders are long thin slots with flat mixing desk caps and a scale of tick lines behind them, for channel levels such as Vocals, Guitar and Master. Tall pills are 64 pixel wide quick-settings controls for volume or a desk lamp, with the icon at the foot and the value above. None of them rotate anything with transform, which used to break layout and pointer maths. They use writing-mode: vertical-lr, which modern browsers apply to range inputs, together with direction: rtl so the minimum sits at the bottom. The box keeps its real upright size in the layout, and pointer, touch and keyboard input all follow the vertical axis. The fill gradient simply runs to top instead of to right. Values such as +4 dB go into the output above each slider and into aria-valuetext.
What’s included
Vertical inputs through writing-mode, no transform rotation
Equaliser fill that grows from a 0 dB centre mark
Mixer faders with flat caps and a tick scale
Quick-settings pills with icon and value
Signed dB readouts such as +4 dB and −6 dB
Accessibility
Each slider is labelled by the visible band or channel name under it and announces its signed value with the unit through aria-valuetext. The arrow keys, Page Up, Page Down, Home and End move each slider as on any range input, with Up raising the value. Focus shows a ring around the thumb or an outline around the pill.
Customise it
Set --vt-accent, --vt-track, --vt-len (slider length), --vt-w (track width), --vt-cross and --vt-along (thumb size across and along the track) on .vt in the plain CSS and Bootstrap versions. In Tailwind change the --color-vt-* theme colours and the h-, w- and -ml- utilities.