beta

Range FX — <mv-range-fx>

Expressive sliders built on one accessible model: the track turns into a wave that ripples as you drag (wave), a value bubble pops up and tilts with velocity (bubble), a ruler scrolls under a fixed needle with inertia (ruler), a liquid cap stretches (fluid), or the label lives inside the track and inverts under the fill (inline). Form-associated.

CategoryPrimitives
TypeWeb Component (<mv-range-fx>)
Statusbeta
Keywordsslider, range, wave, bubble, ruler, fluid, inline-label, spring, velocity, form-associated

When to use

Avoid when

Install

node scripts/add.mjs range-fx --out ./src/marvelous

AI agent with the Marvelous UI MCP server: install_components({ slugs: ["range-fx"], target_dir: "<absolute path>/src/marvelous", framework: "react" }).

Files copied (dependencies included): tokens/tokens.css, core/base.css, core/canvas.js, core/dom.js, core/element.js, core/form.js, core/motion.js, core/observe.js, components/range-fx/range-fx.js, components/range-fx/range-fx.css.

Usage

Canonical markup — start from it and customize with attributes, data-* and CSS variables:

<form id="rfx-demo" style="display:grid;grid-template-columns:repeat(auto-fit,minmax(17rem,1fr));gap:1.25rem;width:100%;max-width:46rem">
  <section style="display:grid;gap:.5rem;padding:1.125rem 1.25rem 1.25rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface)">
    <small style="color:var(--mv-fg-subtle);font-weight:500">wave</small>
    <mv-range-fx variant="wave" label="Volume" name="volume" value="62" value-suffix="%"></mv-range-fx>
  </section>
  <section style="display:grid;gap:.5rem;padding:1.125rem 1.25rem 1.25rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface)">
    <small style="color:var(--mv-fg-subtle);font-weight:500">bubble</small>
    <mv-range-fx variant="bubble" label="Monthly budget" name="budget" min="0" max="2000" step="50" value="850" value-prefix="$"></mv-range-fx>
  </section>
  <section style="grid-column:1/-1;display:grid;gap:.5rem;padding:1.125rem 1.25rem 1rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface)">
    <small style="color:var(--mv-fg-subtle);font-weight:500">ruler — flick the ruler and it settles on the nearest tick</small>
    <mv-range-fx variant="ruler" label="Weight" name="weight" min="70" max="330" step="0.5" major="10" value="151.5" value-suffix=" lb"></mv-range-fx>
  </section>
  <section style="display:grid;gap:.625rem;padding:1.125rem 1.25rem 1.25rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface)">
    <small style="color:var(--mv-fg-subtle);font-weight:500">fluid</small>
    <mv-range-fx variant="fluid" label="Brightness" name="brightness" value="70" value-suffix="%"></mv-range-fx>
  </section>
  <section style="display:grid;gap:.625rem;padding:1.125rem 1.25rem 1.25rem;border:1px solid var(--mv-border);border-radius:var(--mv-radius-xl);background:var(--mv-surface)">
    <small style="color:var(--mv-fg-subtle);font-weight:500">inline</small>
    <mv-range-fx variant="inline" label="Layer opacity" name="opacity" value="40" value-suffix="%"></mv-range-fx>
  </section>
</form>
<script type="module">
  document.getElementById("rfx-demo").addEventListener("submit", (e) => e.preventDefault());
</script>

API

Attributes

NameTypeDefaultDescription
variantwave | bubble | ruler | fluid | inlinewaveRendering and velocity-driven effect.
min / max / stepnumber0 / 100 / 1Bounds and step (step decimals are respected, e.g. 0.5).
valuenumberInitial value, restored on form reset.
namestringForm field name.
labelstringVisible label (above, or inside the track for fluid/inline) and accessible name. Otherwise use aria-label / aria-labelledby.
value-prefix / value-suffixstringText around the formatted value (Intl).
large-stepnumber10% of the rangeStep for PageUp/PageDown and Shift+Arrow.
majornumber10ruler: a labeled major tick every N steps.
stopsnumber10inline: number of intervals marked with dots (hidden under the text).
disabled / readonly / requiredbooleanStandard states.

Properties

NameTypeDescription
value / valueAsNumberstring / numberCurrent value; setting it animates the slider.
formatter(value) => stringCustom formatting (display and aria-valuetext).
form / labels / validityVia ElementInternals.

Methods

NameDescription
focus()Focuses the slider.

Events

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

CSS classes

NameDescription
mv-range-fx-head / -label / -valueLabel + value header.
mv-range-fx-controlFocusable role="slider" element.
mv-range-fx-thumb / -wave / -bubble / -needle / -scale / -fill / -cap / -bar / -stops / -textVariant-specific parts.

CSS variables

NameDefaultDescription
--mv-range-fx-colorvar(--mv-accent)Color of the fill, wave and needle.
--mv-range-fx-tick-gap10pxruler: gap between two ticks.
--mv-range-fx-bubble-bg / -fgbubble: bubble colors.

Accessibility

role="slider" on the control area, with aria-valuenow/min/max, a formatted aria-valuetext (prefix/suffix) and a name taken from the label. Keyboard: arrows (±step, reversed in RTL), Shift+Arrow and PageUp/PageDown (large step), Home/End. All decorations are aria-hidden. Reduced motion: springs, ripple and inertia are disabled and the value jumps into place.