← Blog
12 Oct 2026Vue 3Range sliderAccessibilityVue components

Vue JS Slider: Build an Accessible Range Control in Vue 3

Build a Vue JS slider with native range input and v-model, then check constraints, accessibility, styling, dual-thumb needs, and production tests.

Vue JS Slider: Build an Accessible Range Control in Vue 3

TL;DR

For a single bounded value in Vue 3, start with a labeled native <input type="range">, initialize its value in Vue, bind it with v-model.number, and show the current value and units. Use a number input when people must enter an exact figure. Choose a component or a carefully tested custom implementation only when you need more control over the track, thumb, or interaction. A two-thumb interval is a different problem from a single-value slider.

A search for vue js slider can mean an image carousel or a numeric control. This guide is about the numeric kind: a range input for settings, dashboard filters, and adjustable application parameters. It walks from the control decision through a working Vue example and a release check, rather than teaching a carousel or cataloging every component prop.

Table of contents

Before you start

You need a Vue 3 application, a place to add a single-file component, and one setting with a defined unit, minimum, maximum, increment, and starting value. The example below uses a confidence threshold from 0 to 100 percent in steps of 5. No slider package is required for the native version. If the value is saved to a service, you also need your application’s API and its validation contract; the example deliberately does not implement a backend.

I would decide whether a value can be approximate before choosing a slider. A range input always presents a selected position and is designed for cases where the exact figure is less important than its place on a scale. If people need to type precisely 37.5, give them an editable numeric control instead. MDN’s range-input reference explains this distinction and the native control’s numeric behavior.

1. Choose the control that matches the decision

Ask what the person is actually deciding. An approximate AI confidence threshold or audio level can make sense on a one-thumb scale. A quantity that must be entered exactly is usually easier as a number input. A filter with both a lower and upper limit calls for a two-value interface, not a second visual handle painted onto a one-value input.

Decision flow contrasting exact numeric entry, single-value slider, and two-thumb range selector

For an exact quantity, inspect our Vue Number Input playground, which shows a numeric model alongside min, max, step, labels, and a description. For a single adjustable value, our Range Input playground shows the current value and options for track, progress, control, and handle classes. These are different control choices, not interchangeable skins for the same interaction. DOM Studio’s Number Input reference and Range Input reference document those respective options.

Expected result: you can state whether the field is one approximate value, one exact value, or an interval, and what value the application stores. If you cannot: write the value contract with the product owner first. A visually appealing slider does not resolve ambiguous precision or empty-state requirements.

2. Define bounds, increments, and the initial value

For the example, the contract is min: 0, max: 100, step: 5, and initial: 40, with percent as the unit. Check that the starting value is on the permitted step grid. Decide whether externally loaded values that are out of range should be rejected, normalized with an explanation, or sent back for correction. Do not silently treat a value changed by the browser as proof that it was valid in your data store.

Native range inputs default to a selected value rather than an empty string; the browser constrains values to the declared bounds and handles stepping from a step base. required and readonly do not apply to type="range". If your form needs an unanswered state, do not use the thumb’s initial position to mean “no answer.” Present an explicit opt-in or a different input pattern. MDN documents range defaults, clamping, step behavior, and inapplicable attributes.

A single thumb should also have one canonical value in application state. Avoid keeping a separate unsynchronized percentage for the fill, one value for the label, and a third for submission. Derive presentation from the same model. For a range from min to max, a visual progress percentage is (value - min) / (max - min) * 100; guard against equal bounds before using that formula. If decimal increments matter, define a consistent rounding and server-validation rule rather than comparing arbitrary floating-point values for exact equality.

Expected result: the selected value has a unit and unambiguous range, and your initial state is valid. If the thumb cannot move: check that min is less than max, that step is positive, and that the field is not disabled.

3. Bind a native slider to Vue state

Add this component to a Vue 3 application. It needs only Vue’s ref and a native input. The button records a local submission for demonstration; replace the save function with your application’s request and server-error handling before treating the choice as persisted.

<script setup lang="ts">
import { ref } from 'vue'

const confidence = ref(40)
const submittedConfidence = ref<number | null>(null)

function save() {
  submittedConfidence.value = confidence.value
  // Application-specific API persistence and error handling go here.
}
</script>

<template>
  <form @submit.prevent="save">
    <label for="confidence">Confidence threshold</label>
    <p id="confidence-help">Choose 0 to 100 percent in steps of 5.</p>

    <input
      id="confidence"
      v-model.number="confidence"
      type="range"
      name="confidence"
      min="0"
      max="100"
      step="5"
      aria-describedby="confidence-help"
      :aria-valuetext="`${confidence} percent`"
    />

    <output for="confidence">{{ confidence }}%</output>
    <button type="submit">Apply threshold</button>
    <p v-if="submittedConfidence !== null">
      Applied locally: {{ submittedConfidence }}%
    </p>
  </form>
</template>

<style scoped>
input[type="range"] {
  display: block;
  width: min(100%, 28rem);
  accent-color: #5140a3;
}

input[type="range"]:focus-visible {
  outline: 3px solid #242057;
  outline-offset: 5px;
}
</style>

Vue uses the bound JavaScript value as the source of truth for native inputs, and .number requests numeric conversion; the native range input otherwise exposes its value as a string. Initializing confidence in the script is more reliable than relying on a static HTML value attribute. See Vue’s form-binding guide and MDN’s range-input value reference. The aria-valuetext expresses the percent unit in words; keep it synchronized if the unit or displayed value changes, as described in the W3C slider pattern.

Expected result: dragging the thumb or using the keyboard updates the displayed percentage, and Apply threshold copies the numeric value into submittedConfidence. If the value appears to revert: check for a parent or data loader overwriting the ref. If an API supplies the initial value, validate it against the range and step policy before binding it. A failed network save needs its own recoverable error state; this local example does not provide one.

4. Verify names, focus, keyboard, and touch behavior

Do not assume that a visible number beside the slider supplies an accessible name. The example’s <label> names the control, its hint gives context, and its output shows the selected value. The native input has an implicit slider role, so do not replace it with a styled div and assume the same semantics follow. In a custom slider, the focusable thumb, current value, allowed bounds, accessible name, and keyboard interaction all have to be implemented and tested. MDN lists the native range input’s implicit role; the W3C slider pattern describes custom-widget requirements.

Keyboard focus and touch target for an accessible single-value slider

Tab to the input and try the arrow keys, Home, and End. The W3C pattern expects arrows to adjust a slider by one step and Home and End to reach its limits; Page Up and Page Down are optional larger changes for a custom implementation. Check actual behavior in your supported browser and assistive-technology combinations rather than assuming a design-system demo covers them. W3C also cautions that touch-based assistive technologies can have difficulty operating custom sliders, so include a real-device check. See W3C’s keyboard and touch guidance.

Keep a visible focus indicator after styling and enough usable touch area around the interactive control. If a visual handle grows on hover but the hit area does not, the design may look easier to use than it is. The example uses :focus-visible to preserve a clear keyboard focus state; MDN explains the selector and why removing focus indicators is harmful.

Expected result: a keyboard user can find the control, learn its purpose and unit, and change its value without dragging. If a screen reader reports an unexplained number: check the associated label, range description, and spoken value text in the specific browser and screen reader you support.

5. Customize only what the interaction can support

Start with native color and width. The CSS accent-color property applies to supported range controls, although the exact visual treatment is browser-dependent. Beyond that, track, fill, and thumb styling varies by browser and often calls for browser-specific selectors. Keep the native input focusable and interactive when adding decorative layers; test the actual hit area at narrow widths. MDN documents accent-color support for range inputs.

If you need separate track, progress, control, and handle treatments, evaluate a component with those surfaces rather than replacing the control with pointer events alone. The current DOM Studio Range Input reference exposes trackClass, progressClass, controlClass, handleClass, and activeHandleClass alongside its numeric settings and a visible-value option. Its documentation describes one current numeric value; do not infer dual-thumb support from the name. Verify the version and entry point installed in your project against the current component docs before copying example imports. DOM Studio’s Range Input documentation provides the configuration details.

Make disabled and invalid states distinct in your design. A disabled setting should communicate why it cannot be changed; a rejected saved value should preserve the chosen value and show a correction, not quietly recolor the track. Native range inputs do not provide a genuinely blank required state, so a separate form rule may be necessary when a choice must be explicitly confirmed. Reduce or remove motion effects when the user requests reduced motion, and check contrast of the focus indication and current-value text.

For a visual example of a custom Vue slider treatment, this video shows a range slider with a warning effect. Treat it as styling inspiration, not evidence that its keyboard or screen-reader behavior meets your requirements.

Expected result: styling changes the appearance without removing the label, focus ring, keyboard operation, or usable touch area. If a new visual handle responds only to dragging: restore the native control or implement and test full slider semantics before shipping.

6. Treat two-thumb selection as a separate component decision

A minimum-and-maximum price filter needs two related values. Two native one-thumb inputs can provide two separately labeled controls when a shared-rail presentation is unnecessary, but you must make their bounds and cross-field rule clear. A single-value slider or single-value component cannot become an accessible two-thumb control merely by drawing another circle.

For a shared track with two thumbs, specify whether the handles can cross, their minimum permitted separation, which thumb receives focus first, and how a changed lower limit constrains the upper one. The W3C multi-thumb slider pattern requires each thumb in a stable tab sequence and the dependent thumb’s programmatic minimum or maximum to update when the other thumb changes. It also recommends testing touch assistive technology before production use.

Expected result: each endpoint has a meaningful name and keyboard path, and changing one endpoint cannot create an invalid submitted interval. If your candidate library shows only a screenshot of a dual-handle track: ask for a working keyboard and screen-reader demo, not just a prop list. Do not claim the single-value DOM Studio Range Input supplies this behavior without a documented example.

7. Run a release check against the value contract

Test a small matrix against the 0 to 100, step-5 example. Start at 40; press an arrow key and check that the value and output move together. Move to 0 and 100 with keyboard and pointer input. Resize to a narrow viewport and use touch on a real device. Submit at each endpoint and verify your handler receives a number. Then load an externally supplied invalid value and verify your application applies its chosen rejection or normalization policy. A range input normally constrains an interacted-with value, but it cannot validate an API request that bypasses the browser. MDN covers the control’s bounds and step behavior.

Also test a disabled state if your product uses one, a failed save followed by correction, visible focus, screen-reader announcements, and reduced-motion styling. For an interval selector, test endpoints touching, moving apart, trying to cross, and changing both in a fixed Tab order. Record which browser, screen reader, and touch device you actually checked, especially when a custom component replaces native behavior. W3C’s slider and multi-thumb guidance explain why those interaction checks matter.

Expected result: the displayed, accessible, submitted, and server-accepted values follow the same contract. If they disagree: fix the state mapping or validation boundary rather than adding another visual patch to the thumb.

Frequently asked questions

Should I use v-model or v-model.number for a range input?

Use v-model.number when the Vue state should be numeric, and initialize that state in JavaScript. Vue’s form-binding documentation explains both numeric conversion and the JavaScript-side source of truth. If you wrap the native input in your own component, follow Vue’s documented modelValue and update:modelValue contract, or defineModel() on Vue 3.4 and later; Vue’s component-model guide covers both.

Is a Vue slider the same as an image carousel?

No. A numeric slider adjusts a value on a scale; an image carousel changes which slide or item is shown. This guide implements the numeric range control. For a dual-ended interval, use a multi-thumb design with two independently operable values, not carousel navigation.

Finish with a control you can verify

A labeled native range input is the practical starting point for one approximate numeric setting. The working example supplies Vue value binding, visible units, focus styling, and a local submit action; your next step is to connect your own persistence and run the release checks in the browsers and assistive technologies you support. If your interface needs a configurable single-value track and handle, explore the DOM Studio Range Input playground and compare its documented behavior with your interaction contract.

Sources

Recommended Reads