Peel
import { useEffect, useRef, useState } from 'react';
const W = 240;
const MID = 22;
const H = 18;
const REST = 16;
// Torn ends: a short zigzag rather than a clean cut.
const tear = (x: number, y0: number, y1: number, dir: 1 | -1) => Array.from({ length: 7 }, (_, i) => `${x + dir * (i % 2 ? 2.2 : 0)},${y0 + (i * (y1 - y0)) / 6}`).join('L');
const css = `
.slider-peel { position: relative; width: ${W}px; height: 62px; }
.slider-peel__svg { display: block; width: ${W}px; height: 44px; overflow: visible; }
.slider-peel__track { fill: light-dark(#f3f4f7, #1a1c1f); stroke: light-dark(#ebedf0, #232529); stroke-width: 1; }
.slider-peel__tape { fill: color-mix(in oklab, light-dark(#5f626a, #a7abb2) 55%, transparent); }
.slider-peel__crepe { stroke: light-dark(#50535b, #c6c8cd); stroke-width: 0.5; }
/* Folded back, the sticky side faces up: lighter, smoother, and lifted off the tape on a shadow. */
/* Lighter than the laid tape in both themes: the sticky side catches the light. */
.slider-peel__flap { fill: light-dark(light-dark(#cbced3, #393c41), color-mix(in oklab, light-dark(#50535b, #c6c8cd) 70%, light-dark(#6d7179, #7d8087))); }
.slider-peel__fold { stop-color: light-dark(#373a40, #f1f2f3); }
.slider-peel__shade { stop-color: light-dark(#cbced3, #393c41); }
.slider-peel__crease { stroke: light-dark(#373a40, #f1f2f3); stroke-width: 1; opacity: 0.7; }
.slider-peel__ring { fill: none; stroke: transparent; stroke-width: 2; }
.slider-peel:has(.slider-peel__input:focus-visible) .slider-peel__ring { stroke: light-dark(#50535b, #c6c8cd); }
.slider-peel__value { position: absolute; left: 0; right: 0; top: 44px; display: flex; justify-content: space-between; font-size: 12px; color: light-dark(#5f626a, #a7abb2); }
.slider-peel__value b { color: light-dark(#373a40, #f1f2f3); font-weight: 500; font-variant-numeric: tabular-nums; }
.slider-peel__input { position: absolute; left: 0; top: 0; width: 100%; height: 44px; margin: 0; opacity: 0; cursor: pointer; -webkit-appearance: none; appearance: none; background: none; }
.slider-peel__input::-webkit-slider-thumb { -webkit-appearance: none; width: 28px; height: 44px; }
.slider-peel__input::-moz-range-thumb { width: 28px; height: 44px; border: 0; }
`;
/** The fold trails the value, and its speed decides how far the flap lifts: pulling back peels a longer flap. */
function useFold(target: number) {
const [s, setS] = useState({ x: target, lift: 0 });
const st = useRef({ x: target, v: 0, lift: 0, lv: 0, raf: 0 });
useEffect(() => {
const q = st.current;
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
Object.assign(q, { x: target, v: 0, lift: 0, lv: 0 });
setS({ x: target, lift: 0 });
return;
}
let last = performance.now();
const step = (now: number) => {
const dt = Math.min(0.032, (now - last) / 1000);
last = now;
q.v += (-260 * (q.x - target) - 30 * q.v) * dt;
q.x += q.v * dt;
// Pulling back (negative speed) lifts the flap; pressing forward lays it flatter.
const want = Math.max(-6, Math.min(26, -q.v * 0.07));
q.lv += (-240 * (q.lift - want) - 16 * q.lv) * dt;
q.lift += q.lv * dt;
if (Math.abs(q.x - target) < 0.05 && Math.abs(q.v) < 1 && Math.abs(q.lift) < 0.05 && Math.abs(q.lv) < 0.5) {
Object.assign(q, { x: target, v: 0, lift: 0, lv: 0 });
setS({ x: target, lift: 0 });
return;
}
setS({ x: q.x, lift: q.lift });
q.raf = requestAnimationFrame(step);
};
cancelAnimationFrame(q.raf);
q.raf = requestAnimationFrame(step);
return () => cancelAnimationFrame(q.raf);
}, [target]);
return s;
}
export function PeelSlider() {
const [v, setV] = useState(70);
const { x, lift } = useFold(28 + (v / 100) * (W - 30));
// The flap is the tape folded back over itself from the fold at x; its free end is what you hold.
const flap = Math.max(4, Math.min(x - 3, REST + lift));
const end = x - flap;
const top = MID - H / 2 - 1.5;
const bot = MID + H / 2 + 1.5;
const flapPath = `M${x - 4},${top}Q${x},${top} ${x},${top + 4}L${x},${bot - 4}Q${x},${bot} ${x - 4},${bot}L${tear(end, bot, top, 1)}Z`;
return (
<>
<style>
{css}
</style>
<div className="slider-peel">
<svg className="slider-peel__svg" aria-hidden="true">
<defs>
{/* Bright where it rounds over the fold, falling into shade along the flap towards its free end. */}
<linearGradient id="slider-peel-fold" x1="1" y1="0" x2="0" y2="0">
<stop className="slider-peel__fold" offset="0" stopOpacity="0.6" />
<stop className="slider-peel__fold" offset="0.18" stopOpacity="0" />
<stop className="slider-peel__shade" offset="1" stopOpacity="0.35" />
</linearGradient>
</defs>
<rect className="slider-peel__track" x={0.5} y={MID - H / 2 - 3} width={W - 1} height={H + 6} rx={6} />
<path className="slider-peel__tape" d={`M${tear(3, MID - H / 2, MID + H / 2, -1)}L${x},${MID + H / 2}L${x},${MID - H / 2}Z`} />
{/* Crepe paper: faint, uneven creases across the strip. */}
{Array.from({ length: Math.max(0, Math.floor((x - 6) / 3.5)) }, (_, i) => (
<line
key={i}
className="slider-peel__crepe"
x1={6 + i * 3.5}
x2={6.6 + i * 3.5}
y1={MID - H / 2 + 1}
y2={MID + H / 2 - 1}
style={{ opacity: [0.22, 0.1, 0.16, 0.07, 0.13][i % 5] }}
/>
))}
{/* Folded back, the flap is a little wider than the tape and its torn end lifts off the surface. */}
<path
className="slider-peel__flap"
d={flapPath}
style={{
filter: `drop-shadow(${-2 - lift * 0.15}px ${2.5 + Math.max(0, lift) * 0.12}px ${2.5 + Math.max(0, lift) * 0.1}px light-dark(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.7)))`,
}}
/>
<path d={flapPath} fill="url(#slider-peel-fold)" />
<line className="slider-peel__crease" x1={x - 0.5} x2={x - 0.5} y1={top + 1} y2={bot - 1} />
<rect className="slider-peel__ring" x={end - 4} y={top - 4} width={flap + 8} height={bot - top + 8} rx={6} />
</svg>
<p className="slider-peel__value" aria-hidden="true">
<span>Coverage</span>
<b>{v}%</b>
</p>
<input className="slider-peel__input" type="range" min={0} max={100} value={v} onChange={(e) => setV(+e.target.value)} aria-label="Coverage" aria-valuetext={`${v}%`} />
</div>
</>
);
}Markup where the component goes, CSS in your stylesheet, script before </body>. In Vue, Svelte or Angular, call initSliderPeel(el) on the mounted element instead.
<div class="slider-peel">
<svg class="slider-peel__svg" aria-hidden="true">
<defs>
<linearGradient id="slider-peel-fold" x1="1" y1="0" x2="0" y2="0">
<stop class="slider-peel__fold" offset="0" stop-opacity="0.6"></stop>
<stop class="slider-peel__fold" offset="0.18" stop-opacity="0"></stop>
<stop class="slider-peel__shade" offset="1" stop-opacity="0.35"></stop>
</linearGradient>
</defs>
<rect class="slider-peel__track" x="0.5" y="10" width="239" height="24" rx="6"></rect>
<path class="slider-peel__tape" d="M3,13L0.8,16L3,19L0.8,22L3,25L0.8,28L3,31L175,31L175,13Z"></path>
<line class="slider-peel__crepe" x1="6" x2="6.6" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="9.5" x2="10.1" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="13" x2="13.6" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="16.5" x2="17.1" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="20" x2="20.6" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="23.5" x2="24.1" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="27" x2="27.6" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="30.5" x2="31.1" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="34" x2="34.6" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="37.5" x2="38.1" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="41" x2="41.6" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="44.5" x2="45.1" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="48" x2="48.6" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="51.5" x2="52.1" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="55" x2="55.6" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="58.5" x2="59.1" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="62" x2="62.6" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="65.5" x2="66.1" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="69" x2="69.6" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="72.5" x2="73.1" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="76" x2="76.6" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="79.5" x2="80.1" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="83" x2="83.6" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="86.5" x2="87.1" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="90" x2="90.6" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="93.5" x2="94.1" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="97" x2="97.6" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="100.5" x2="101.1" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="104" x2="104.6" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="107.5" x2="108.1" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="111" x2="111.6" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="114.5" x2="115.1" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="118" x2="118.6" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="121.5" x2="122.1" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="125" x2="125.6" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="128.5" x2="129.1" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="132" x2="132.6" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="135.5" x2="136.1" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="139" x2="139.6" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="142.5" x2="143.1" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="146" x2="146.6" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="149.5" x2="150.1" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="153" x2="153.6" y1="14" y2="30" style="opacity:0.16"></line>
<line class="slider-peel__crepe" x1="156.5" x2="157.1" y1="14" y2="30" style="opacity:0.07"></line>
<line class="slider-peel__crepe" x1="160" x2="160.6" y1="14" y2="30" style="opacity:0.13"></line>
<line class="slider-peel__crepe" x1="163.5" x2="164.1" y1="14" y2="30" style="opacity:0.22"></line>
<line class="slider-peel__crepe" x1="167" x2="167.6" y1="14" y2="30" style="opacity:0.1"></line>
<line class="slider-peel__crepe" x1="170.5" x2="171.1" y1="14" y2="30" style="opacity:0.16"></line>
<path class="slider-peel__flap" d="M171,11.5Q175,11.5 175,15.5L175,28.5Q175,32.5 171,32.5L159,32.5L161.2,29L159,25.5L161.2,22L159,18.5L161.2,15L159,11.5Z" style="filter:drop-shadow(-2px 2.5px 2.5px light-dark(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.7)))"></path>
<path d="M171,11.5Q175,11.5 175,15.5L175,28.5Q175,32.5 171,32.5L159,32.5L161.2,29L159,25.5L161.2,22L159,18.5L161.2,15L159,11.5Z" fill="url(#slider-peel-fold)"></path>
<line class="slider-peel__crease" x1="174.5" x2="174.5" y1="12.5" y2="31.5"></line>
<rect class="slider-peel__ring" x="155" y="7.5" width="24" height="29" rx="6"></rect>
</svg>
<p class="slider-peel__value" aria-hidden="true">
<span>Coverage</span>
<b>70%</b>
</p>
<input class="slider-peel__input" type="range" min="0" max="100" aria-label="Coverage" aria-valuetext="70%" value="70"/>
</div>.slider-peel { position: relative; width: 240px; height: 62px; }
.slider-peel__svg { display: block; width: 240px; height: 44px; overflow: visible; }
.slider-peel__track { fill: light-dark(#f3f4f7, #1a1c1f); stroke: light-dark(#ebedf0, #232529); stroke-width: 1; }
.slider-peel__tape { fill: color-mix(in oklab, light-dark(#5f626a, #a7abb2) 55%, transparent); }
.slider-peel__crepe { stroke: light-dark(#50535b, #c6c8cd); stroke-width: 0.5; }
/* Folded back, the sticky side faces up: lighter, smoother, and lifted off the tape on a shadow. */
/* Lighter than the laid tape in both themes: the sticky side catches the light. */
.slider-peel__flap { fill: light-dark(light-dark(#cbced3, #393c41), color-mix(in oklab, light-dark(#50535b, #c6c8cd) 70%, light-dark(#6d7179, #7d8087))); }
.slider-peel__fold { stop-color: light-dark(#373a40, #f1f2f3); }
.slider-peel__shade { stop-color: light-dark(#cbced3, #393c41); }
.slider-peel__crease { stroke: light-dark(#373a40, #f1f2f3); stroke-width: 1; opacity: 0.7; }
.slider-peel__ring { fill: none; stroke: transparent; stroke-width: 2; }
.slider-peel:has(.slider-peel__input:focus-visible) .slider-peel__ring { stroke: light-dark(#50535b, #c6c8cd); }
.slider-peel__value { position: absolute; left: 0; right: 0; top: 44px; display: flex; justify-content: space-between; font-size: 12px; color: light-dark(#5f626a, #a7abb2); }
.slider-peel__value b { color: light-dark(#373a40, #f1f2f3); font-weight: 500; font-variant-numeric: tabular-nums; }
.slider-peel__input { position: absolute; left: 0; top: 0; width: 100%; height: 44px; margin: 0; opacity: 0; cursor: pointer; -webkit-appearance: none; appearance: none; background: none; }
.slider-peel__input::-webkit-slider-thumb { -webkit-appearance: none; width: 28px; height: 44px; }
.slider-peel__input::-moz-range-thumb { width: 28px; height: 44px; border: 0; }function initSliderPeel(root) {
var W = 240;
var MID = 22;
var H = 18;
var REST = 16;
var NS = 'http://www.w3.org/2000/svg';
var input = root.querySelector('.slider-peel__input');
var svg = root.querySelector('.slider-peel__svg');
var tape = root.querySelector('.slider-peel__tape');
var flap = root.querySelector('.slider-peel__flap');
var sheen = flap.nextElementSibling;
var crease = root.querySelector('.slider-peel__crease');
var ring = root.querySelector('.slider-peel__ring');
var label = root.querySelector('.slider-peel__value b');
var top = MID - H / 2 - 1.5;
var bot = MID + H / 2 + 1.5;
// Crepe paper: faint, uneven creases across the strip, enough for a full-length tape.
root.querySelectorAll('.slider-peel__crepe').forEach(function (l) { l.remove(); });
var crepe = [];
for (var i = 0; i * 3.5 + 6 < W; i++) {
var l = document.createElementNS(NS, 'line');
l.setAttribute('class', 'slider-peel__crepe');
l.setAttribute('x1', 6 + i * 3.5);
l.setAttribute('x2', 6.6 + i * 3.5);
l.setAttribute('y1', MID - H / 2 + 1);
l.setAttribute('y2', MID + H / 2 - 1);
l.style.opacity = [0.22, 0.1, 0.16, 0.07, 0.13][i % 5];
svg.insertBefore(l, flap);
crepe.push(l);
}
var q = { x: target(), v: 0, lift: 0, lv: 0 };
var raf = 0;
function target() { return 28 + (+input.value / 100) * (W - 30); }
// Torn ends: a short zigzag rather than a clean cut.
function tear(x, y0, y1, dir) {
var pts = [];
for (var i = 0; i < 7; i++) pts.push(x + dir * (i % 2 ? 2.2 : 0) + ',' + (y0 + (i * (y1 - y0)) / 6));
return pts.join('L');
}
function draw() {
var x = q.x;
// The flap is the tape folded back over itself from the fold at x; its free end is what you hold.
var flapLen = Math.max(4, Math.min(x - 3, REST + q.lift));
var end = x - flapLen;
var d = 'M' + (x - 4) + ',' + top + 'Q' + x + ',' + top + ' ' + x + ',' + (top + 4) + 'L' + x + ',' + (bot - 4) + 'Q' + x + ',' + bot + ' ' + (x - 4) + ',' + bot + 'L' + tear(end, bot, top, 1) + 'Z';
var lift = Math.max(0, q.lift);
tape.setAttribute('d', 'M' + tear(3, MID - H / 2, MID + H / 2, -1) + 'L' + x + ',' + (MID + H / 2) + 'L' + x + ',' + (MID - H / 2) + 'Z');
crepe.forEach(function (l, i) { l.style.display = 6 + i * 3.5 < x - 3 ? '' : 'none'; });
flap.setAttribute('d', d);
// The lifted flap casts a shadow that grows as it lifts.
flap.style.filter = 'drop-shadow(' + (-2 - q.lift * 0.15) + 'px ' + (2.5 + lift * 0.12) + 'px ' + (2.5 + lift * 0.1) + 'px light-dark(rgba(0, 0, 0, 0.25), rgba(0, 0, 0, 0.7)))';
sheen.setAttribute('d', d);
crease.setAttribute('x1', x - 0.5);
crease.setAttribute('x2', x - 0.5);
ring.setAttribute('x', end - 4);
ring.setAttribute('width', flapLen + 8);
}
// The fold trails the value; pulling back (negative speed) lifts the flap, pressing forward lays it flatter.
function run() {
cancelAnimationFrame(raf);
label.textContent = input.value + '%';
input.setAttribute('aria-valuetext', input.value + '%');
var goal = target();
if (matchMedia('(prefers-reduced-motion: reduce)').matches) {
q = { x: goal, v: 0, lift: 0, lv: 0 };
return draw();
}
var last = performance.now();
function step(now) {
var dt = Math.min(0.032, (now - last) / 1000);
last = now;
q.v += (-260 * (q.x - goal) - 30 * q.v) * dt;
q.x += q.v * dt;
var want = Math.max(-6, Math.min(26, -q.v * 0.07));
q.lv += (-240 * (q.lift - want) - 16 * q.lv) * dt;
q.lift += q.lv * dt;
if (Math.abs(q.x - goal) < 0.05 && Math.abs(q.v) < 1 && Math.abs(q.lift) < 0.05 && Math.abs(q.lv) < 0.5) {
q = { x: goal, v: 0, lift: 0, lv: 0 };
return draw();
}
draw();
raf = requestAnimationFrame(step);
}
raf = requestAnimationFrame(step);
}
input.addEventListener('input', run);
draw();
return function () {
cancelAnimationFrame(raf);
input.removeEventListener('input', run);
};
}
document.querySelectorAll('.slider-peel').forEach(initSliderPeel);Goal: Create a "Peel" slider.
Visual behavior: A coverage slider whose fill is a strip of masking tape with its end folded back: drag back to peel it up, with the flap lifting higher the faster you pull, and forward to press it down.
Technique:
- A real <input type="range"> lies over the drawing, transparent (opacity 0, appearance none), with its thumb hit area sized to the drawn thumb, so dragging, clicking the track and the arrow, Page Up/Down, Home and End keys all work natively. The drawing is aria-hidden.
- The laid tape is a translucent path with a torn start and faint, uneven crepe lines. The flap is the tape folded back over itself from the fold: a lighter path with a rounded fold edge, a torn free end and a gradient that is bright at the fold and falls into shade along the flap.
- The fold follows the value on a spring. A second spring sets the flap length from the speed, longer when pulling back and shorter when pressing forward, and the drop shadow under it grows with the lift.
Accessibility: A native range input with an aria-label and an aria-valuetext in percent; keyboard focus rings the flap. Under prefers-reduced-motion the tape moves without the springs and the flap stays at rest.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: neutral only. Black, white and grays, no hues. Use the color values above as written, or my project's closest neutral tokens.
Return a single self-contained component. No dependencies.Goal: Create a "Peel" slider.
Visual behavior: A coverage slider whose fill is a strip of masking tape with its end folded back: drag back to peel it up, with the flap lifting higher the faster you pull, and forward to press it down.
Technique:
- A real <input type="range"> lies over the drawing, transparent (opacity 0, appearance none), with its thumb hit area sized to the drawn thumb, so dragging, clicking the track and the arrow, Page Up/Down, Home and End keys all work natively. The drawing is aria-hidden.
- The laid tape is a translucent path with a torn start and faint, uneven crepe lines. The flap is the tape folded back over itself from the fold: a lighter path with a rounded fold edge, a torn free end and a gradient that is bright at the fold and falls into shade along the flap.
- The fold follows the value on a spring. A second spring sets the flap length from the speed, longer when pulling back and shorter when pressing forward, and the drop shadow under it grows with the lift.
Accessibility: A native range input with an aria-label and an aria-valuetext in percent; keyboard focus rings the flap. Under prefers-reduced-motion the tape moves without the springs and the flap stays at rest.
My stack: {{USER_STACK}}
My styling: {{USER_STYLING}}
My constraints: {{USER_CONSTRAINTS}}
My colors: use my project's existing color tokens. The color values above are grayscale placeholders. Map each one by its role (surface, text, border, accent, glow) to my theme, and keep their relative lightness so contrast and the effect still read.
Return a single self-contained component. No dependencies.Paste into Claude, ChatGPT or Cursor after filling in {{USER_STACK}}, {{USER_STYLING}} and {{USER_CONSTRAINTS}}. Neutral keeps the greys you see here; Match my project asks the model to map them onto your own colour tokens.
Free to use in any project, commercial included. MIT-0, no attribution required. Licence
About this component
A coverage slider whose fill is a strip of masking tape with its end folded back: drag back to peel it up, with the flap lifting higher the faster you pull, and forward to press it down.
- Dependencies
- None
- Difficulty
- Advanced
- Triggers
- state change, spring, fill
- Techniques
- svg, spring, raf, gradient
- Reduced motion
- Respected
- Theme
- Light and dark (follows your page's color-scheme)
A native range input with an aria-label and an aria-valuetext in percent; keyboard focus rings the flap. Under prefers-reduced-motion the tape moves without the springs and the flap stays at rest.