Skip to content

Slider Volume

Use a vertical slider with custom thumb and tooltip slots and a mute control.

Code

The following example assumes that you have imported the component and set up the theme.

Volume Slider
<div class="slider-wrapper">
<div class="slider-container">
<!-- Button that toggles mute and displays the correct volume icon -->
<button type="button" class="toggle-mute-btn" data-state="volume-medium" title="Toggle mute">
<!-- Icon: Full volume -->
5 collapsed lines
<svg class="icon volume-full" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path
d="M561.54-155.62v-62q86.54-27.53 139.42-100 52.89-72.46 52.89-163.38t-52.89-163.38q-52.88-72.47-139.42-100v-62q111.69 29.92 182 119.92 70.3 90 70.3 205.46 0 115.46-70.3 205.46-70.31 90-182 119.92ZM146.16-380v-200h148.46l171.53-171.53v543.06L294.62-380H146.16Zm415.38 46.15v-294.3q40.46 22 62.54 61.96 22.07 39.96 22.07 86.19 0 45.61-22.27 84.88-22.27 39.27-62.34 61.27Z"
></path>
</svg>
<!-- Icon: Muted -->
5 collapsed lines
<svg class="icon volume-off" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path
d="M778.92-74.46 658.31-195.08q-20.77 13.31-43.77 23.08-23 9.77-47.62 16.38v-62q12.85-4.61 25-9.42 12.16-4.81 23-11.42L471.54-381.85v173.38L300-380H151.54v-200h121.85L79.85-773.54 122-815.69l699.07 699.07-42.15 42.16Zm-19.54-216.23-43-43q20.85-32.54 31.85-69.81t11-77.5q0-90.92-52.88-163.38-52.89-72.47-139.43-100v-62Q679-776.46 749.11-686.46q70.12 90 70.12 205.46 0 52.62-15.65 101.04-15.66 48.42-44.2 89.27ZM636.92-413.15l-70-70v-145q40.46 22 62.54 61.96T651.54-480q0 17.69-3.66 34.5-3.65 16.81-10.96 32.35ZM471.54-578.54l-86.31-86.69 86.31-86.3v172.99Z"
></path>
</svg>
<!-- Icon: Low volume -->
3 collapsed lines
<svg class="icon volume-low" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path d="M300-380v-200h148.46L620-751.53v543.06L448.46-380H300Z"></path>
</svg>
<!-- Icon: Medium volume -->
5 collapsed lines
<svg class="icon volume-medium" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path
d="M220-380v-200h148.46L540-751.53v543.06L368.46-380H220Zm415.38 46.15v-294.3q38.85 21 61.73 60.96Q720-527.23 720-480t-22.89 86.19q-22.88 38.96-61.73 59.96Z"
></path>
</svg>
</button>
<!-- Vertical volume slider component -->
<sv-slider class="volume-slider" aria-label="Volume" max="150" thumb-contained value="50" vertical>
<!-- Tooltip showing the current slider value -->
<div aria-hidden="true" slot="tooltip">
<strong>
<sv-text-flow data-value></sv-text-flow>
</strong>
</div>
<!-- Custom thumb replacing the default slider thumb -->
<div class="custom-thumb" aria-hidden="true" slot="thumb-1">
5 collapsed lines
<svg class="icon" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg">
<path
d="M400-120q-66 0-113-47t-47-113q0-66 47-113t113-47q23 0 42.5 5.5T480-418v-422h240v160H560v400q0 66-47 113t-113 47Z"
></path>
</svg>
</div>
</sv-slider>
</div>
</div>
<script type="module">
/** @type {Slider} */
const slider = document.querySelector(".volume-slider");
/** @type {HTMLDivElement} */
const muteToggleBtn = document.querySelector(".toggle-mute-btn");
// Update icon when slider value changes
slider.addEventListener("valuechange", event => {
setIconState(event.detail.value);
});
// Toggle mute/unmute by clicking the button
muteToggleBtn.addEventListener("click", () => {
const currentValue = slider.value;
if (Array.isArray(currentValue)) return;
const value = currentValue === slider.min ? slider.max / 2 : slider.min;
slider.setValue({
value,
animate: true,
});
setIconState(value);
});
/** @param {number} value */
function setIconState(value) {
if (value === 0) {
muteToggleBtn.dataset.state = "volume-off";
return;
}
if (value < 50) {
muteToggleBtn.dataset.state = "volume-low";
return;
}
if (value < 100) {
muteToggleBtn.dataset.state = "volume-medium";
return;
}
muteToggleBtn.dataset.state = "volume-full";
}
</script>
<style>
23 collapsed lines
.slider-wrapper {
display: flex;
flex-direction: column;
gap: 1em;
align-items: center;
padding: 2em;
background: linear-gradient(135deg, #ff6a00, #7a00ff);
border-radius: 4px;
}
.slider-container {
display: flex;
flex-direction: column;
gap: 1em;
align-items: center;
padding: 6px;
background-color: var(--sv-gray-6);
border-radius: 2em;
box-shadow: 0 0 0.5em #00000030;
transition: scale 350ms cubic-bezier(0.34, 1.5, 0.64, 1);
}
/* Slight scale effect when the slider is actively used */
.slider-container:has(.volume-slider:state(--active)) {
scale: 1.05;
}
.volume-slider {
--sv-thickness: 2em;
--sv-thumb-size: 2em;
--sv-bdr-rad-md: 2em;
--sv-tooltip-offset: 2em;
/* Required height for vertical slider */
min-block-size: 200px;
cursor: grab;
&:state(--active) {
cursor: grabbing;
}
/* Slider track styling */
&::part(track) {
background: var(--sv-gray-5);
border: none;
}
/* Tooltip styling */
&::part(tooltip) {
min-inline-size: 1.8em;
color: var(--sv-accent-content);
background-color: var(--sv-accent);
border-color: transparent;
box-shadow: 0 0 0.5em #00000030;
}
&::part(tooltip-arrow) {
fill: var(--sv-accent);
stroke: transparent;
}
/* Custom thumb appearance */
.custom-thumb {
display: flex;
align-items: center;
justify-content: center;
inline-size: var(--sv-thumb-size);
block-size: var(--sv-thumb-size);
background-color: var(--sv-accent);
border-radius: var(--sv-bdr-rad-md);
}
39 collapsed lines
}
.icon {
inline-size: 1.4em;
block-size: 1.4em;
fill: var(--sv-accent-content);
}
.toggle-mute-btn {
display: grid;
place-items: center;
inline-size: 2.5em;
block-size: 2.5em;
color: var(--sv-accent-content);
cursor: pointer;
background-color: var(--sv-accent);
border: none;
border-radius: 500px;
.icon {
display: none;
}
&[data-state="volume-medium"] .icon.volume-medium {
display: block;
}
&[data-state="volume-full"] .icon.volume-full {
display: block;
}
&[data-state="volume-off"] .icon.volume-off {
display: block;
}
&[data-state="volume-low"] .icon.volume-low {
display: block;
}
}
@media (prefers-reduced-motion: reduce) {
.slider-container {
transition-duration: 0s;
}
}
</style>