// Keeps each slider's filled track (--p) and its <output> in step with the value.
document.querySelectorAll('.md input[type="range"]').forEach(function (range) {
var field = range.closest('.md');
var out = field.querySelector('output');
function sync() {
var text = (range.dataset.prefix || '') + Number(range.value).toLocaleString('en-US') + (range.dataset.suffix || '');
field.style.setProperty('--p', (range.value - range.min) / (range.max - range.min));
if (out) out.textContent = text;
range.setAttribute('aria-valuetext', text);
}
range.addEventListener('input', sync);
sync();
});
// Mute button: remembers the last volume, drops the slider to 0 and restores it on the second press.
document.querySelectorAll('.md-mute').forEach(function (btn) {
var range = document.getElementById(btn.getAttribute('aria-controls')), last = range.value;
btn.addEventListener('click', function () {
if (+range.value > 0) { last = range.value; range.value = 0; } else { range.value = +last > 0 ? last : 50; }
range.dispatchEvent(new Event('input'));
});
range.addEventListener('input', function () { btn.setAttribute('aria-pressed', String(+range.value === 0)); });
});
// Keeps each slider's filled track (--p) and its <output> in step with the value.
document.querySelectorAll('.md .form-range').forEach(function (range) {
var field = range.closest('.md');
var out = field.querySelector('output');
function sync() {
var text = (range.dataset.prefix || '') + Number(range.value).toLocaleString('en-US') + (range.dataset.suffix || '');
field.style.setProperty('--p', (range.value - range.min) / (range.max - range.min));
if (out) out.textContent = text;
range.setAttribute('aria-valuetext', text);
}
range.addEventListener('input', sync);
sync();
});
// Mute button: remembers the last volume, drops the slider to 0 and restores it on the second press.
document.querySelectorAll('.md-mute').forEach(function (btn) {
var range = document.getElementById(btn.getAttribute('aria-controls')), last = range.value;
btn.addEventListener('click', function () {
if (+range.value > 0) { last = range.value; range.value = 0; } else { range.value = +last > 0 ? last : 50; }
range.dispatchEvent(new Event('input'));
});
range.addEventListener('input', function () { btn.setAttribute('aria-pressed', String(+range.value === 0)); });
});
// Keeps each slider's filled track (--p) and its <output> in step with the value.
document.querySelectorAll('.md input[type="range"]').forEach(function (range) {
var field = range.closest('.md');
var out = field.querySelector('output');
function sync() {
var text = (range.dataset.prefix || '') + Number(range.value).toLocaleString('en-US') + (range.dataset.suffix || '');
field.style.setProperty('--p', (range.value - range.min) / (range.max - range.min));
if (out) out.textContent = text;
range.setAttribute('aria-valuetext', text);
}
range.addEventListener('input', sync);
sync();
});
// Mute button: remembers the last volume, drops the slider to 0 and restores it on the second press.
document.querySelectorAll('.md-mute').forEach(function (btn) {
var range = document.getElementById(btn.getAttribute('aria-controls')), last = range.value;
btn.addEventListener('click', function () {
if (+range.value > 0) { last = range.value; range.value = 0; } else { range.value = +last > 0 ? last : 50; }
range.dispatchEvent(new Event('input'));
});
range.addEventListener('input', function () { btn.setAttribute('aria-pressed', String(+range.value === 0)); });
});
About this set
Sliders for media players, smart home panels and system settings, each with inline SVG icons. Volume with a mute button places a speaker button on the left of the track and a loud speaker icon on the right; pressing the button remembers the level, drops the slider to zero and brings the level back on the next press. Brightness with sun icons uses a small and a large sun and an amber to orange gradient fill. Control-centre pills are 48 pixel tall tracks in the style of phone quick settings, with the icon and the percentage drawn inside and a slim white handle where the fill ends. Colour temperature has no fill at all: the track is the scale itself, from warm orange to cool blue, with a dark-ringed thumb. All of them are real range inputs, and the icons, percentages and handle are layered with CSS so the whole pill stays one draggable control. A short script updates the fill, the readouts and aria-valuetext, and a few more lines run the mute button.
What’s included
Mute button that restores the previous volume
Sun icons and a gradient fill for brightness
Control-centre pills with icon and value inside the track
Colour temperature track that is its own scale
Decorative icons hidden from assistive technology
Accessibility
Every slider has a visible label. The mute button is a real button named Mute with aria-pressed, which turns true whenever the volume sits at zero, and aria-controls pointing at the slider. Icons are aria-hidden. Focus shows a ring on the thumb, or an outline around the whole pill where the handle is too slim to carry one.
Customise it
On .md change --md-accent, --md-track, --md-thumb and --md-h; .warm, .pill and .temp override them for their variants (plain CSS and Bootstrap). In Tailwind edit the --color-md-* theme colours; the brightness gradient and temperature stops sit in the track background utilities.