Slider Color Picker
Use three sliders to edit RGB channels and use-diff to animate the value.
Code
The following example assumes that you have imported the component and set up the theme.
<div class="slider-wrapper"> <!-- Color preview -->6 collapsed lines
<div class="preview-container"> <div class="color-preview"></div> <strong> <sv-text-flow class="color-value" use-diff></sv-text-flow> </strong> </div>
<!-- Red slider -->6 collapsed lines
<div class="slider-container"> <label for="red-slider">Red</label> <sv-slider id="red-slider" class="color-slider" max="255" value="100"> <div class="custom-thumb" aria-hidden="true" slot="thumb-1"></div> </sv-slider> </div>
<!-- Green slider -->6 collapsed lines
<div class="slider-container"> <label for="green-slider">Green</label> <sv-slider id="green-slider" class="color-slider green" max="255"> <div class="custom-thumb" aria-hidden="true" slot="thumb-1"></div> </sv-slider> </div>
<!-- Blue slider -->6 collapsed lines
<div class="slider-container"> <label for="blue-slider">Blue</label> <sv-slider id="blue-slider" class="color-slider" max="255"> <div class="custom-thumb" aria-hidden="true" slot="thumb-1"></div> </sv-slider> </div>
<!-- Color swatches -->10 collapsed lines
<div class="swatches-container"> <button type="button" style="background: #f00" aria-label="Red" data-color="255 0 0"></button> <button type="button" style="background: #0f0" aria-label="Green" data-color="0 255 0"></button> <button type="button" style="background: #00f" aria-label="Blue" data-color="0 0 255"></button> <button type="button" style="background: #ff0" aria-label="Yellow" data-color="255 255 0"></button> <button type="button" style="background: #f0f" aria-label="Magenta" data-color="255 0 255"></button> <button type="button" style="background: #0ff" aria-label="Cyan" data-color="0 255 255"></button> <button type="button" style="background: #000" aria-label="Black" data-color="0 0 0"></button> <button type="button" style="background: #fff" aria-label="White" data-color="255 255 255"></button> </div></div>
<script>110 collapsed lines
/** @type {HTMLDivElement} */ const wrapper = document.querySelector(".slider-wrapper");
/** @type {Slider} */ const redSlider = document.querySelector("#red-slider");
/** @type {Slider} */ const greenSlider = document.querySelector("#green-slider");
/** @type {Slider} */ const blueSlider = document.querySelector("#blue-slider");
/** @type {HTMLSpanElement} */ const previewValue = document.querySelector(".color-value");
/** @type {NodeListOf<HTMLButtonElement>} */ const swatches = document.querySelectorAll(".swatches-container button");
const prefersReducedMotion = matchMedia("(prefers-reduced-motion: reduce)");
CSS.registerProperty({ name: "--red", syntax: "<number>", inherits: true, initialValue: "0", });
CSS.registerProperty({ name: "--green", syntax: "<number>", inherits: true, initialValue: "0", });
CSS.registerProperty({ name: "--blue", syntax: "<number>", inherits: true, initialValue: "0", });
const color = { red: getChannelValue(redSlider), green: getChannelValue(greenSlider), blue: getChannelValue(blueSlider), };
/** @param {MouseEvent} event */ function swatchClickHandler(event) { /** @type {HTMLButtonElement} */ const button = event.currentTarget;
// Swatch new RGB values const [nR, nG, nB] = button.dataset.color.split(" ").map(Number);
const duration = prefersReducedMotion.matches ? 0 : 300; const easing = "cubic-bezier(0.25, 1, 0.5, 1)";
// Old RGB values const [oR, oG, oB] = [redSlider, greenSlider, blueSlider].map(getChannelValue);
// Animate gradients if (duration > 0) { wrapper.animate( [ { "--red": oR, "--green": oG, "--blue": oB }, { "--red": nR, "--green": nG, "--blue": nB }, ], { duration, easing: "linear" } ); }
// Set the new value with animation const animate = duration > 0; redSlider.setValue({ value: nR, animate, duration, easing }); greenSlider.setValue({ value: nG, animate, duration, easing }); blueSlider.setValue({ value: nB, animate, duration, easing });
onColorChange(); }
function onColorChange() { color.red = getChannelValue(redSlider); color.green = getChannelValue(greenSlider); color.blue = getChannelValue(blueSlider);
wrapper.style.setProperty("--red", String(color.red)); wrapper.style.setProperty("--green", String(color.green)); wrapper.style.setProperty("--blue", String(color.blue));
const previewText = `RGB(${color.red}, ${color.green}, ${color.blue})`; previewValue.textContent = previewText; }
/** @param {Slider} slider */ function getChannelValue(slider) { const value = slider.value; if (Array.isArray(value)) return value[0] ?? slider.min; return value; }
onColorChange();
redSlider.addEventListener("valuechange", onColorChange); greenSlider.addEventListener("valuechange", onColorChange); blueSlider.addEventListener("valuechange", onColorChange);
for (const swatch of swatches) { swatch.addEventListener("click", swatchClickHandler); }</script>
<style> .slider-wrapper { --red: 0; --green: 0; --blue: 0;
4 collapsed lines
display: flex; flex-direction: column; gap: 2em; padding: 1em; }
.preview-container {2 collapsed lines
display: flex; gap: 1em; }
.color-preview {5 collapsed lines
inline-size: 4em; block-size: 2em; background-color: rgb(var(--red) var(--green) var(--blue)); border: 1px solid #fff; border-radius: var(--sv-bdr-rad-md); }
.slider-container {3 collapsed lines
display: flex; flex-direction: column; gap: 0.5em; }
.color-slider { --sv-thickness: 1.8em; --sv-thumb-size: 2.2em; --sv-bdr-rad-md: 2em; --sv-bdr-sz-sm: 2px; --sv-tooltip-offset: 2em; --sv-ease: cubic-bezier(0.25, 1, 0.5, 1);
&::part(slider) { box-shadow: 0 0 7px #00000060; }
&::part(track) { border-color: #fff; }
&::part(fill) { background-color: transparent; }
&::part(tooltip) { background-color: transparent; border: none; }
&::part(tooltip-arrow) { display: none; }
.custom-thumb {29 collapsed lines
position: relative; inline-size: var(--sv-thumb-size); block-size: var(--sv-thumb-size); background-color: #fff; border-radius: 50%; box-shadow: 0 0 7px 0 #00000080; transition-timing-function: var(--sv-ease); transition-duration: var(--sv-dur); transition-property: inline-size, block-size;
&::after { position: absolute; inset-block-start: 50%; inset-inline-start: 50%; z-index: -1; inline-size: var(--sv-thumb-size); block-size: var(--sv-thumb-size); margin: auto; content: ""; background-color: rgb(255 255 255 / 40%); border-radius: 50%; box-shadow: 0 0 7px 0 #00000080; backdrop-filter: blur(2px); translate: -50% -50%; }
@media (prefers-reduced-motion: reduce) { transition-duration: 0s; } }
&:state(--active) { cursor: grabbing;
.custom-thumb { inline-size: 4px; block-size: 4px; } } }
#red-slider::part(track) { background: linear-gradient(90deg, rgb(0 var(--green) var(--blue)), rgb(255 var(--green) var(--blue))); }
#green-slider::part(track) { background: linear-gradient(90deg, rgb(var(--red) 0 var(--blue)), rgb(var(--red) 255 var(--blue))); }
#blue-slider::part(track) { background: linear-gradient(90deg, rgb(var(--red) var(--green) 0), rgb(var(--red) var(--green) 255)); }
.swatches-container {13 collapsed lines
display: flex; flex-wrap: wrap; gap: 0.5em; justify-content: center;
/* Swatch */ button { inline-size: 2em; block-size: 2em; cursor: pointer; border: 1px solid var(--sl-color-text); border-radius: var(--sv-bdr-rad-md); } }</style>