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.

CategoryPrimitives
TypeWeb Component (<mv-slider>)
Statusstable
Keywordsslider, range, dual-thumb, ticks, vertical, equalizer, form

When to use

Avoid when

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

NameTypeDefaultDescription
min / max / stepnumber0 / 100 / 1Bounds and step (values snap; the step’s decimals are respected).
valuestringDefault value; “20,80” = two thumbs (one thumb per value). Restored on form reset.
namestringField name: one FormData entry per thumb.
labelstringAccessible name (suffixed “minimum / maximum” with two thumbs). Otherwise use aria-labelledby on the host.
thumb-labelsstringComma-separated names, one per thumb.
orientationhorizontal | verticalhorizontalDirection; the vertical length comes from --mv-slider-length.
ticksbooleanOne dot per step on the track (up to 101).
marksstringLabeled marks under the track: “0,50,100” or “0:Min,100:Max”.
tooltip"" | alwaysValue bubble on hover, keyboard focus and while dragging; always = always visible.
value-prefix / value-suffixstringText around the formatted value (Intl, document language).
originnumberminFill starting point for a single thumb (e.g. 0 on −12…+12: bipolar fill).
large-stepnumber10% of the rangeStep for PageUp/PageDown and Shift+arrow.
disabledbooleanDisables the slider (also inherited from a <fieldset disabled>).
data-sizesm | lgSize (host or native input).

Properties

NameTypeDescription
valuestringCurrent value (“42” or “20,80”).
valuesnumber[]Current values, sorted.
formatter(value, index) => stringCustom formatting (bubble, aria-valuetext, marks).
form / labelsVia ElementInternals.

Methods

NameDescription
focus()Focuses the first thumb.
syncSliderFill(input)Module export: resyncs a native input’s fill after setting input.value = … in JS.

Events

NameDescription
mv-inputOn every change (drag, keyboard). detail: { value, values }.
mv-changeEnd of interaction (release, key press). detail: { value, values }.

CSS classes

NameDescription
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

NameTypeDefaultDescription
--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..1Set 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.