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.
| Category | Primitives |
|---|---|
| Type | Web Component (<mv-range-fx>) |
| Status | beta |
| Keywords | slider, range, wave, bubble, ruler, fluid, inline-label, spring, velocity, form-associated |
When to use
- A showcase or creative tool needs a slider with personality, such as a rippling wave, tilting bubble or liquid cap
- A value is picked on a ruler that scrolls under a fixed needle, like a measurement or tuning control
- The label and value must live inside the track to save space
Avoid when
- Dual thumbs, ticks, labeled marks or a sober look for dense forms are needed → use Slider instead
- The fill should slosh like liquid with inertia, or the control should look like a vertical tank → use Slosh Slider instead
Install
node scripts/add.mjs range-fx --out ./src/marvelousAI 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
| Name | Type | Default | Description |
|---|---|---|---|
variant | wave | bubble | ruler | fluid | inline | wave | Rendering and velocity-driven effect. |
min / max / step | number | 0 / 100 / 1 | Bounds and step (step decimals are respected, e.g. 0.5). |
value | number | Initial value, restored on form reset. | |
name | string | Form field name. | |
label | string | Visible label (above, or inside the track for fluid/inline) and accessible name. Otherwise use aria-label / aria-labelledby. | |
value-prefix / value-suffix | string | Text around the formatted value (Intl). | |
large-step | number | 10% of the range | Step for PageUp/PageDown and Shift+Arrow. |
major | number | 10 | ruler: a labeled major tick every N steps. |
stops | number | 10 | inline: number of intervals marked with dots (hidden under the text). |
disabled / readonly / required | boolean | Standard states. |
Properties
| Name | Type | Description |
|---|---|---|
value / valueAsNumber | string / number | Current value; setting it animates the slider. |
formatter | (value) => string | Custom formatting (display and aria-valuetext). |
form / labels / validity | Via ElementInternals. |
Methods
| Name | Description |
|---|---|
focus() | Focuses the slider. |
Events
| Name | Description |
|---|---|
mv-input | On every value change (drag, wheel, keyboard). detail: { value }. |
mv-change | End of interaction (release, ruler stop, key press). |
CSS classes
| Name | Description |
|---|---|
mv-range-fx-head / -label / -value | Label + value header. |
mv-range-fx-control | Focusable role="slider" element. |
mv-range-fx-thumb / -wave / -bubble / -needle / -scale / -fill / -cap / -bar / -stops / -text | Variant-specific parts. |
CSS variables
| Name | Default | Description |
|---|---|---|
--mv-range-fx-color | var(--mv-accent) | Color of the fill, wave and needle. |
--mv-range-fx-tick-gap | 10px | ruler: gap between two ticks. |
--mv-range-fx-bubble-bg / -fg | bubble: 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.