EmSlider
A value slider with pointer drag, click-to-position, and the full slider keyboard map — one thumb by default, more through the slot.
Usage
v-model is a plain number by default, and the value stays a number the whole way through — drag the thumb, click the track, or use the arrow keys, and the model updates with a number snapped to step and clamped to min/max. Bind an array instead and the slider switches to array-in, array-out; that is how a range slider works, covered under Examples.
Clicking anywhere on the track is not ignored: the nearest thumb jumps to the click point and the drag starts from there, so a reader can press-and-slide in one motion without first finding the 16-pixel thumb.
Always pass label — it is the accessible name for the slider, and without it a screen reader announces a generic “Slider”.
Anatomy
<script setup lang="ts">
import { EmSlider } from '@paper/emerald'
</script>
<template>
<EmSlider />
</template>Composed on v0
EmSlider renders v0’s Slider compound — Slider.Root wrapping a default anatomy of Slider.Track, Slider.Range and a single Slider.Thumb.
The ownership split is clean: v0 owns every behavior, Emerald owns every pixel. Pointer drag (tracked on the document, so a drag that leaves the track keeps working), click-to-position on the track, the keyboard map, step snapping, the ARIA slider attributes, and the hidden form inputs all come from the v0 parts. Emerald contributes the wrapper element, the emerald-slider__* classes, and the stylesheet that turns v0’s data-orientation, data-disabled, data-readonly and data-state attributes into a styled control — it never writes a state class of its own.
The default slot replaces the built-in track and thumb while keeping Slider.Root and its state. Anything you place there resolves the same slider context, which is how the range example supplies two thumbs. Emerald does not re-export the v0 parts, so slot content imports Slider from @vuetify/v0 and borrows Emerald’s classes — the stylesheet is global, so emerald-slider__thumb styles a v0 Slider.Thumb exactly as it styles the built-in one.
One consequence of the wrapper: Slider.Root emits start and end drag events, but EmSlider does not forward them — its root element is the wrapper div, not the v0 root. React to changes through v-model.
Examples
Props
| Prop | Type | Default | Description |
|---|---|---|---|
v-model | number | number[] | 0 | Current value. Number in, number out; bind an array when supplying multiple thumbs through the slot |
min | number | 0 | Minimum value |
max | number | 100 | Maximum value |
step | number | 1 | Increment every input path snaps to |
orientation | 'horizontal' | 'vertical' | 'horizontal' | Layout axis. Vertical sliders fill upward |
inverted | boolean | false | Flips the percent axis, so the maximum sits at the track’s start. Horizontal arrow keys flip with it |
disabled | boolean | false | Blocks pointer and keyboard edits and removes the thumb from the tab order |
readonly | boolean | false | Blocks edits but keeps the thumb focusable and announced |
label | string | — | Accessible name for the slider group; also names the default thumb |
ariaLabel | string | — | Accessible name for the default thumb; wins over label on the thumb |
ariaLabelledby | string | — | ID of an element that labels the group, instead of label |
minStepsBetweenThumbs | number | 0 | Minimum gap between adjacent thumbs, in steps |
crossover | boolean | false | Lets thumbs pass through each other |
name | string | — | Form field name. Renders one hidden input per thumb |
form | string | — | Associates the hidden inputs with a form by ID |
id | string | auto-generated | Identifier for the underlying slider context. Not rendered as a DOM id |
namespace | string | — | Context the v0 slider parts resolve against. Leave unset except when nesting sliders |
The default slot replaces the built-in track, range and thumb — see the range example. There are no named slots, and nothing is exposed through a template ref.
With name set, submission works like a native field: one <input type="hidden"> per thumb, all sharing the name, each synced to its thumb’s value and disabled together with the slider.
Accessibility
The group element carries role="group" named by label (or ariaLabelledby), and each thumb is a role="slider" element with aria-valuenow, aria-valuemin, aria-valuemax and aria-orientation — the standard WAI-ARIA slider surface, kept in sync by v0 on every input path.
Naming
The thumb’s accessible name resolves as ariaLabel, else label, else a locale-provided “Slider” fallback. The fallback exists so the control is never anonymous, but a generic “Slider, 40” announcement helps nobody — pass label, and pass a per-thumb ariaLabel whenever there is more than one thumb, because the group name does not distinguish the ends of a range.
In a range, each thumb’s announced aria-valuemin and aria-valuemax are its real limits — clamped at the neighboring thumb rather than the slider’s bounds — so a screen reader hears the range the thumb can actually reach. With crossover enabled the full bounds are announced instead.
Keyboard
Each thumb is a tab stop; all keys operate the focused thumb.
| Key | Behavior |
|---|---|
| Arrow Right | One step up — down when inverted |
| Arrow Left | One step down — up when inverted |
| Arrow Up | One step up |
| Arrow Down | One step down |
| Shift + Arrow | Ten steps in the arrow’s direction |
| Page Up / Page Down | Ten steps up / down |
| Home / End | Jump to minimum / maximum |
Orientation does not remap the arrows — both pairs work on both axes, with up meaning more. The horizontal arrows also do not follow text direction: there is no RTL remap in the current source, so ArrowRight increments under RTL too.
Disabled and readonly
Both stop edits — pointer, track click and keyboard alike — but they present differently:
| State | Thumb tabindex | Announced as | Edits |
|---|---|---|---|
disabled | -1 — skipped when tabbing | aria-disabled | Blocked |
readonly | 0 — still focusable | aria-readonly | Blocked |
Prefer readonly when the value is information the reader should still be able to reach and hear — a keyboard user can focus the thumb and have the value announced. disabled removes the thumb from the tab order entirely, which is right only when the value is currently irrelevant.
Focus
The thumb shows Emerald’s focus ring on :focus-visible only, so keyboard focus gets an indicator and a pointer drag does not leave one behind. While dragging, the thumb carries data-state="dragging" and swaps to a grabbing cursor.