Slider

New

Control for selecting a value or range along a scale

Usage

A slider is appropriate when the exact value is less important than the relative position within a range — volume, brightness, a budget cap, or a price filter. Users can drag the thumb or use keyboard navigation to adjust the value.

Do

  • Selecting an approximate value from a continuous range (volume, brightness, percentage).
  • Filtering results by a numeric range where exact precision isn't critical (price, date offset).

Don’t

  • When precise numeric input is required — use DtInput instead, or pair a slider with a companion input.
  • For a small set of discrete choices, use DtRadio or DtSelectMenu instead.

Best Practices

  • Give the slider a clear label; for range sliders, pass getValueText so each thumb's start/end value is distinguishable (e.g. "Minimum: 20" / "Maximum: 70").
  • Keep min and max values meaningful to the context. Label the scale so users understand what the numbers represent.
  • Pair the slider with a number input when users need precise entry, especially on touch devices (see With companion inputs).
  • Space ticks and marks far enough apart to remain readable at the available width — avoid more than ~20 ticks on a single track.
  • Use a layout gap, such as dt-stack, when stacking sliders with other content.

Variants and Examples

Single thumb

Range slider

Set min-gap-steps to keep a minimum separation between range values. It's a number of steps, not a raw value gap — the actual minimum gap enforced between the thumbs' values is minGapSteps * step; for example, 5 steps of size 10 require a gap of 50:

With companion inputs

Pair a range slider with number inputs for precise entry. Bind the slider to an array and each input to its corresponding start or end value.

With start and end slots (aka left/right)

Icon-only content works the same way — give it an aria-label so the meaning still reaches screen readers:

With ticks

A numeric showTicks interval is independent of step — ticks are purely a visual overlay along the track and don't constrain where the thumb can actually stop. The two often match, but they don't have to.

When the showTicks interval equals step, every value the thumb can land on gets its own tick — same as passing show-ticks="true":

When they differ, ticks become checkpoints along a finer scale rather than a mark for every stop — the thumb still moves by step, ticks just call out the notable positions:

Magnetic snapping

step is a hard restriction — the thumb can only ever land on a value in its grid. snapPoints is different: it pulls a dragged thumb toward nearby values, like the snapping in Figma or Photoshop, but a value just outside a fixed 10px radius along the track stays freely reachable. The pull is also sticky — once a thumb snaps onto a point, dragging away from it takes noticeably more distance than dragging onto it did, so it resists small jitter right at the boundary. It only affects pointer dragging — keyboard stepping via step/largeStep is unaffected.

Pass a number for an evenly spaced interval:

Pass an array for arbitrary snap values — unlike an interval, they don't need to be evenly spaced:

Marks

Marks are text annotations placed below the track at specific positions, independent of ticks — use them together or separately.

Pass an array of numbers to label positions automatically:

Pass { value, text } objects when the label at a position isn't just the number itself — value still positions the mark on the track, but text can be any string. The readout doesn't read from marks, so pair it with getValueText or suffix to keep it consistent with what the marks say:

Marks and ticks are independent — a mark doesn't need a tick at the same position and vice versa, so dense ticks pair well with sparser marks:

Value readout

Set readout to show each thumb's current value. Useful when the track context alone isn't enough to communicate the exact value.

  • tooltip (default) — a tooltip-styled bubble above the thumb, shown only while that thumb is hovered, dragged, or focused. It renders in the browser's top layer so ancestor overflow, clipping paths, and masks cannot cut it off. It shifts or flips near viewport edges and follows the thumb during dragging, scrolling, and resizing. Prefer it in space-constrained or dense layouts — it never reserves space below the slider the way always does, making it the right default for toolbars, side panels, and anywhere several sliders stack tightly.
  • always — a readout below the track, always visible. Reach for this when the value needs to stay legible without any interaction — dashboards, read-mostly summaries, or sliders a user might glance at rather than operate.
  • never — no readout. Use this when you're rendering the value yourself elsewhere on the page (see below).

In range mode with always, the two thumbs' readouts merge into a single centered low–high pill once they'd otherwise overlap as the thumbs converge.

Use readout-class to style the individual and merged readouts in always mode. It has no effect in tooltip or never mode.

Use prefix/suffix to decorate the raw number wherever it's displayed — the readout, marks, and each thumb's aria-valuetext all pick it up:

For anything beyond a fixed prefix/suffix, pass getValueText — it takes precedence and receives the thumb index too, so range sliders can give each thumb distinct text:

To render the value somewhere custom outside the component instead, set readout="never" and drive your own display off the same v-model:

Fill direction

Set fill-origin to end (aka right) to fill from the max end toward the thumb instead of the default start (aka left).

Fill from origin

Set fill-origin to a value within [min, max] and the indicator grows outward from that point toward the thumb, in either direction. Useful for balance controls (fill from center) or deviation-from-setpoint displays (fill from a target value on a signed scale).

Disabled

Vertical orientation

Customize the track length with a CSS utility class directly on the component:

Visually hidden label

Use :show-label="false" when you have a visually obvious context but still need accessible text for screen readers.

Custom label content

The label slot replaces the plain text label entirely — scoped with value (the live value, or [low, high] in range mode) so the label itself can echo the current value or any derived text:

Use dt-text with variant="label-md" and tone="secondary" to match the default label's typography and color.

Accessibility

Keyboard support

Key Action
Arrow Right / Arrow Up Increase value by step
Arrow Left / Arrow Down Decrease value by step
Home Jump to min
End Jump to max
Page Up / Shift + Arrow Right / Shift + Arrow Up Increase value by roughly largeStep (default 10), rounded to the nearest whole number of steps — always at least one
Page Down / Shift + Arrow Left / Shift + Arrow Down Decrease value by roughly largeStep (default 10), rounded to the nearest whole number of steps — always at least one

Screen reader behavior

  • Each thumb is a native <input type="range"> which carries role="slider" implicitly, along with aria-valuemin, aria-valuemax, and aria-valuenow.
  • The component label is associated with each thumb via aria-labelledby. When show-label is false, the label is removed and the label prop's text is promoted to aria-label instead — the same pattern DtInput, DtCheckbox, DtRadio, and DtSelectMenu use. A slotted (non-string) #label can't be promoted this way, so pair a hidden #label slot with an explicit aria-label on the component.
  • For range sliders, provide the getValueText prop to give each thumb a distinct, localized description:
  • When the start or end slots contain icon-only content, add aria-label to each icon so the surrounding context is communicated to screen readers.
  • Formatted values remain available via aria-valuetext even when readout="never" hides the visual readout.

Vue API

Use body-class to style the existing wrapper around the start slot, control, and end slot, such as adjusting the gap between slot content and the control.

import { DtSlider } from '@dialpad/dialtone-vue';

Slots

Name
Type
end

Optional content at the inline-end end of the track (aka right).

label

Slot for the label, defaults to the label prop. Scoped with

start

Optional content at the inline-start end of the track (aka left).

Props

Name
Default
Type
bodyClass
''
string|array|object

Additional class(es) applied to the body wrapper containing the start slot, control, and end slot.

disabled
false
"true" | "false"

Disables the slider, preventing interaction.

endClass
''
string|array|object

Additional class(es) applied to the inline-end slot wrapper (aka right).

fillOrigin
'start'
number|string

Which end the indicator fills from, toward the thumb: "start" (the default, aka left) fills from the min end, "end" (aka right) fills from the max end. Pass a Number instead to fill outward from that value toward the thumb — useful for balance controls (aka center-fill) or deviation-from-setpoint displays. A numeric origin is clamped to [min, max]. Ignored in range mode.

getValueText
null
func

A function returning the user-facing text for a value — shared by the readout and each thumb's aria-valuetext, so the two always agree on how a number is displayed. Signature: (value: number, index?: number) => string. index is the thumb index (use it to differentiate thumbs in range mode, e.g. "Minimum: 20" / "Maximum: 70"). Deliberately NOT used for a mark's own auto-generated text — a mark isn't tied to either thumb, so there's no index this function could meaningfully receive; marks use prefix/suffix instead (see the showMarks prop), or their own explicit text override. Takes precedence over prefix/suffix for the readout and aria-valuetext when set. The default (null) uses the raw number (optionally wrapped in prefix/suffix), which must be i18n-safe for your context.

label
''
string

Visible label text. Required for accessibility; if omitted, provide aria-label on the component.

labelClass
''
string|array|object

Additional class(es) applied to the label wrapper element.

labelSize
null
"100" | "200" | "300" | "400"

Overrides the label text size.

labelStrength
null
"bold" | "semibold" | "medium" | "normal"

Overrides the label font weight.

largeStep
SLIDER_DEFAULT_LARGE_STEP
number

Roughly how far to move on Page Up/Page Down or Shift + Arrow, in the same units as step — rounded to the nearest whole number of steps (at least one), so the result always lands on the step grid and always moves, even when step is coarser than largeStep itself.

max
100
number

The maximum allowed value. Must be finite.

min
0
number

The minimum allowed value. Must be finite.

minGapSteps
0
number

Minimum gap, in steps (not raw values), that must remain between the two thumbs in range mode: the actual minimum gap enforced between their values is minGapSteps * step, so the same prop value means a different real gap depending on step (e.g. minGapSteps={5} enforces a gap of 5 with step={1}, but 50 with step={10}).

modelValue
number|array

The current value. A number enables single-thumb mode; an array of two numbers enables range mode. When omitted (or explicitly undefined/null), resolves to the midpoint of [min, max] — matching native — see normalizeModelValue.

name
''
string

Native name attribute for form submission. In range mode, both inputs share this name. Retrieve both values with FormData.getAll(name).

orientation
'horizontal'
"horizontal" | "vertical"

Track orientation.

prefix
''
string

Text prepended to the raw number wherever it's displayed — e.g. prefix="$" for currency. Always applied to marks. Ignored by the readout and aria-valuetext when getValueText is set (see getValueText). A long prefix makes every mark's text longer too — keep it short enough that the first/last mark stays legible near the track's own edges (see showMarks).

readout
'tooltip'
"tooltip" | "always" | "never"

Controls the live value readout for each thumb: a tooltip-styled bubble above the thumb shown only while hovering, dragging, or focusing it (the default); always visible below the track; or never shown. Text is formatted via getValueText when set, otherwise prefix/suffix — same as each thumb's aria-valuetext, but unlike a mark's own auto-generated text (see showMarks). The tooltip uses the native Popover API to escape ancestor clipping and stays within the viewport. Browsers without that API do not display the floating readout; use always when a visible value is needed there. The inline readout and the merged two-thumb pill are not edge-clamped, so a long formatted value can render close to or past the track's own edge at those values.

readoutClass
''
string|array|object

Additional class(es) applied to the individual and merged inline readouts when readout is 'always'. Ignored when readout is 'tooltip' or 'never'.

showLabel
true
"true" | "false"

When false, the label is removed and its text (the label prop, not the #label slot) is promoted to aria-label instead — matching DtInput, DtCheckbox, DtRadio, and DtSelectMenu's own showLabel behavior.

showMarks
false
array|boolean

Text annotations rendered below the track at specific positions, independent of ticks. Off by default — rendering min/max isn't always wanted. Pass true to label min and max (start and end) only. Pass an array for explicit control: each entry is either a plain number (text defaults to the number itself, formatted with prefix/suffix — NOT getValueText, which has no meaningful index for a position that isn't tied to either thumb) or an object with a required value and optional text override, which is used as-is regardless of prefix/suffix/getValueText. Example: [{ value: 0, text: 'Neutral' }, -100, 100] Mark values must be finite numbers; malformed entries are ignored.

Keep mark text (via a short prefix/suffix, or an explicit text override) reasonably short — a mark landing exactly on min or max is edge-aligned rather than centered, and any other mark is nudged inward once it would otherwise render past the track's own edges, but very long text can still end up close to the live readout or an adjacent mark.

showTicks
false
boolean|number

Renders tick marks along the track. Pass true to put a tick at every step; pass a Number instead to space ticks at that interval (same units as step).

snapPoints
number|array

Magnetic snap points the thumb pulls toward while dragging — unlike step, this doesn't restrict which values are selectable; a value just outside the snap radius of a snap point stays freely reachable. Pass a number for an evenly spaced interval (e.g. 25), or an array for arbitrary values (e.g. [10, 42, 90]). Pointer drag only — keyboard stepping (step/largeStep) is unaffected. Values must be finite numbers; malformed array entries are ignored.

startClass
''
string|array|object

Additional class(es) applied to the inline-start slot wrapper (aka left).

step
1
number

The increment/decrement step. Must be finite.

suffix
''
string

Text appended to the raw number wherever it's displayed — e.g. suffix="%" for a percentage. Always applied to marks. Ignored by the readout and aria-valuetext when getValueText is set (see getValueText). Same edge-legibility caveat as prefix applies (see showMarks).

Events

Name
Type
blur
FocusEvent

Native blur event on a thumb.

change
number | number[]

Emitted when a value change commits: pointer up, blur, or a reactive min/max/step/minGapSteps correction that lands outside any in-progress drag. Use this for API calls or persistence.

focus
FocusEvent

Native focus event on a thumb.

update:modelValue
number | number[]

Emitted on every value change (drag, keyboard, track click).

Slider documentation last updated Tuesday, October 27, 2026