Slider — <mv-slider>
Slider in two forms: a styled native <input type="range" class="mv-slider"> (synced fill), and a form-associated <mv-slider> for advanced cases — dual thumbs, ticks, labeled marks, value bubble, vertical orientation, full keyboard support.
Category Primitives Type Web Component (<mv-slider>) Status stable Keywords slider, range, dual-thumb, ticks, vertical, equalizer, form
When to use
Users adjust an approximate value within a range, such as volume, opacity or zoom A min/max range must be picked with two thumbs, as in a price filter Settings need ticks, labeled marks, a value bubble or a vertical orientation, as in an equalizer
Avoid when
The exact value matters more than its position in the range → use Number Field instead The slider is a signature, expressive control on a showcase or creative page → use Range FX instead
Get Slider. Included in every plan: the Pro pack ships all 226 components with the CLI and the MCP server used below.
See plans
Install
node scripts/add.mjs slider --out ./src/marvelous
AI agent with the Marvelous UI MCP server: install_components({ slugs: ["slider"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).
Files copied (dependencies included): tokens/tokens.css, core/base.css, core/dom.js, core/element.js, components/slider/slider.js, components/slider/slider.css.
Usage
Canonical markup — start from it and customize with attributes, data-* and CSS variables:
<div id="sl-demo" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(16rem,1fr));gap:2.5rem 3rem;width:100%;max-width:46rem;align-items:start;font-size:.875rem">
<div style="display:grid;gap:1.75rem">
<div style="display:grid;gap:.625rem">
<div style="display:flex;justify-content:space-between;font-weight:500"><label for="sl-volume">Volume</label><output for="sl-volume" data-out="volume" style="color:var(--mv-fg-muted);font-variant-numeric:tabular-nums">65%</output></div>
<div style="display:flex;align-items:center;gap:.75rem;color:var(--mv-fg-muted)">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4z"/></svg>
<input type="range" class="mv-slider" id="sl-volume" min="0" max="100" value="65">
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M11 5 6 9H2v6h4l5 4z"/><path d="M15.5 8.5a5 5 0 0 1 0 7M19 5a10 10 0 0 1 0 14"/></svg>
</div>
</div>
<div style="display:grid;gap:.625rem">
<div style="display:flex;justify-content:space-between;font-weight:500"><span id="sl-budget-label">Nightly budget</span><output data-out="budget" style="color:var(--mv-fg-muted);font-variant-numeric:tabular-nums">$80 – $240</output></div>
<mv-slider aria-labelledby="sl-budget-label" name="budget" min="0" max="400" step="10" value="80,240" tooltip value-prefix="$" marks="0,100,200,300,400:$400+"></mv-slider>
</div>
<div style="display:grid;gap:.625rem">
<div style="font-weight:500" id="sl-font-label">Text size</div>
<mv-slider aria-labelledby="sl-font-label" min="12" max="24" step="2" value="16" ticks tooltip value-suffix=" px" marks="12,16,20,24"></mv-slider>
</div>
</div>
<div style="display:grid;gap:.75rem;justify-items:center">
<div style="font-weight:500;justify-self:start">Equalizer</div>
<div style="display:flex;gap:1.25rem;justify-content:center;padding:.75rem 1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-lg);background:var(--mv-surface);box-shadow:var(--mv-shadow-xs)">
<div style="display:grid;justify-items:center;gap:.5rem"><mv-slider orientation="vertical" label="60 Hz" min="-12" max="12" origin="0" value="4" tooltip value-suffix=" dB" style="--mv-slider-length:9rem"></mv-slider><small style="color:var(--mv-fg-muted)">60</small></div>
<div style="display:grid;justify-items:center;gap:.5rem"><mv-slider orientation="vertical" label="250 Hz" min="-12" max="12" origin="0" value="7" tooltip value-suffix=" dB" style="--mv-slider-length:9rem"></mv-slider><small style="color:var(--mv-fg-muted)">250</small></div>
<div style="display:grid;justify-items:center;gap:.5rem"><mv-slider orientation="vertical" label="1 kHz" min="-12" max="12" origin="0" value="1" tooltip value-suffix=" dB" style="--mv-slider-length:9rem"></mv-slider><small style="color:var(--mv-fg-muted)">1k</small></div>
<div style="display:grid;justify-items:center;gap:.5rem"><mv-slider orientation="vertical" label="4 kHz" min="-12" max="12" origin="0" value="-3" tooltip value-suffix=" dB" style="--mv-slider-length:9rem"></mv-slider><small style="color:var(--mv-fg-muted)">4k</small></div>
<div style="display:grid;justify-items:center;gap:.5rem"><mv-slider orientation="vertical" label="16 kHz" min="-12" max="12" origin="0" value="5" tooltip value-suffix=" dB" style="--mv-slider-length:9rem"></mv-slider><small style="color:var(--mv-fg-muted)">16k</small></div>
</div>
</div>
</div>
<script type="module">
// Demo: mirror values into the <output> labels.
const root = document.getElementById("sl-demo");
const vol = root.querySelector("#sl-volume");
const volOut = root.querySelector('[data-out="volume"]');
vol.addEventListener("input", () => (volOut.textContent = `${vol.value}%`));
const budgetOut = root.querySelector('[data-out="budget"]');
root.querySelector('mv-slider[name="budget"]').addEventListener("mv-input", (e) => {
const [lo, hi] = e.detail.values;
budgetOut.textContent = `$${lo} – ${hi === 400 ? "$400+" : `$${hi}`}`;
});
</script>
API
Attributes
Name Type Default Description min / max / stepnumber 0 / 100 / 1Bounds and step (values snap; the step’s decimals are respected). valuestring Default value; “20,80” = two thumbs (one thumb per value). Restored on form reset. namestring Field name: one FormData entry per thumb. labelstring Accessible name (suffixed “minimum / maximum” with two thumbs). Otherwise use aria-labelledby on the host. thumb-labelsstring Comma-separated names, one per thumb. orientationhorizontal | vertical horizontalDirection; the vertical length comes from --mv-slider-length. ticksboolean One dot per step on the track (up to 101). marksstring Labeled marks under the track: “0,50,100” or “0:Min,100:Max”. tooltip"" | always Value bubble on hover, keyboard focus and while dragging; always = always visible. value-prefix / value-suffixstring Text around the formatted value (Intl, document language). originnumber minFill starting point for a single thumb (e.g. 0 on −12…+12: bipolar fill). large-stepnumber 10% of the rangeStep for PageUp/PageDown and Shift+arrow. disabledboolean Disables the slider (also inherited from a <fieldset disabled>). data-sizesm | lg Size (host or native input).
Properties
Name Type Description valuestring Current value (“42” or “20,80”). valuesnumber[] Current values, sorted. formatter(value, index) => string Custom formatting (bubble, aria-valuetext, marks). form / labelsVia ElementInternals.
Methods
Name Description focus()Focuses the first thumb. syncSliderFill(input)Module export: resyncs a native input’s fill after setting input.value = … in JS.
Events
Name Description mv-inputOn every change (drag, keyboard). detail: { value, values }. mv-changeEnd of interaction (release, key press). detail: { value, values }.
CSS classes
Name Description mv-sliderOn <input type="range">: styled track, fill and thumb (Blink, WebKit, Firefox). mv-slider-control / -track / -range / -thumb / -bubble / -tick / -marks / -markStyleable parts generated by <mv-slider>.
CSS variables
Name Type Default Description --mv-slider-thumb-size1.125remThumb diameter. --mv-slider-track-size0.375remTrack thickness. --mv-slider-length10remLength in vertical orientation. --mv-slider-fill-colorvar(--mv-accent)Color of the selected range (<mv-slider>). --mv-slider-bubble-bg / -fgBubble colors. --mv-slider-marks-width2.5remWidth of the marks column in vertical mode. --mv-slider-fill0..1 Set by the script on the native input (fill ratio).
Accessibility
Each thumb has role="slider", aria-valuenow/min/max (dynamic bounds: thumbs never cross), a formatted aria-valuetext and aria-orientation. Keyboard: arrows (±step, reversed in RTL), Shift+arrow and PageUp/PageDown (large step), Home/End. Name via label, thumb-labels or aria-labelledby (each thumb keeps its suffix). Clicking an associated <label for> moves focus. The native input keeps all of the browser’s built-in accessibility.