Eunomia v1.0.0-beta.2
AllAngularReactNext.jsJavaScriptVue
EunomiaSalmonForestVioletOceanGoldFireCustom…
🇬🇧 English🇫🇷 Français

Slider

Slider lets someone pick a single numeric value out of a bounded range by dragging a thumb along a track, such as a volume level, a brightness setting, or a price ceiling. It builds on a real native range field restyled to match the rest of the library, so dragging and every keyboard interaction come from the browser itself rather than a hand-built control.

Dependencies

eun-label
Overview API Examples Accessibility

When to use

Use a slider whenever someone is picking one numeric value out of a bounded, continuous-feeling range where the relative position matters as much as the exact number, such as volume, brightness, a price ceiling, or a percentage. If the value needs to be typed precisely more often than dragged, or there's no natural minimum and maximum to anchor a track to, a counter (bounded, stepped, keyboard and typing first) or a plain numeric input communicates that better. See Alternatives below.

Install & usage

Pick a framework in the toolbar above and these snippets adapt.

npm install @eunomia/elements
import "@eunomia/elements/slider.js";
<eun-slider name="volume" label="Volume" value="40"></eun-slider>

Importing the file registers <eun-slider> as a custom element, with no further setup needed. It works with any framework, or none, since it's a standard web component.

Alternatives

You want .. Prefers A value typed precisely more often than dragged eun-counter A plain numeric field with no natural minimum or maximum to anchor a track to eun-input

Guidance

  • Always set label (or slot equivalent content), since a slider with no accessible name is a critical screen-reader failure (see the Accessibility tab)
  • Set min/max to a range that's actually meaningful for the value being picked, not the defaults (0100) left unexamined
  • Use graduated with a deliberate tick-step when a few reference points genuinely help (e.g. every 25%), not one tick per single-unit step
  • Relying on the floating tooltip alone to convey the current value, since it's purely decorative (aria-hidden) and the accessible value comes from the native slider itself
  • Using a slider for a value that's just as easily, and more precisely, typed, since that's what counter or a numeric input is for
  • Overriding colors with inline styles instead of the --slider-* CSS variables

Live testing

Properties

Slider <eun-slider>

Attributes

NameTypeDefaultDescription
default-valuenumberThe default value applied when the field connects, and restored on form reset
hide-errorbooleanfalseWhether the errors are hidden
hide-tooltipbooleanfalseHides the floating value bubble that otherwise appears above the thumb while dragging or focused
tick-stepnumberThe spacing between graduation marks while graduated is set. Defaults to step itself
minnumber0The minimum value allowed
maxnumber100The maximum value allowed
stepnumber1The granularity the value must follow, applied natively by the browser while dragging or using the keyboard
graduatedbooleanfalseDraws decorative graduation marks along the track, spaced by tickStep, defaulting to step
valuenumberThe current numeric value

Import the exact TypeScript type behind any property above, see Types.

Properties

JS-only — no matching HTML attribute, set these from a script or a template binding.

NameTypeDefaultDescription
labelstringThe title label of the field, rendered through the label component
instructionsstringInstructions displayed below the label
namestringThe field name
disabledbooleanfalseWhether the field is disabled
readonlybooleanfalseWhether the field is read only
requiredbooleanfalsePresent for API consistency with every other field, but inert here, since a slider's value can never be empty the way a text field's can
hideErrorbooleanfalseWhether the errors are hidden
validatorsArray<Validators<number>>The list of validation rules applied to the field value

Slots

NameDescription
labelCustom label content, falling back to the label property
instructionsCustom instructions content, falling back to the instructions property

Events

NameTypeDescription
eunchangeChangeEventFired on every value change, live while dragging or typing

Every event above follows the same naming convention, covered in Events.

CSS custom properties

NameDescription
--slider-track-heightSets the height of the track
--slider-track-backgroundSets the background color of the unfilled portion of the track
--slider-track-fill-backgroundSets the background color of the filled portion of the track, from the minimum edge up to the thumb
--slider-track-border-radiusSets the corner radius of the track
--slider-thumb-sizeSets the width and height of the thumb
--slider-thumb-backgroundSets the color of the thumb
--slider-thumb-border-widthSets the width of the border around the thumb
--slider-thumb-border-colorSets the border color around the thumb, defaulting to the current fill color
--slider-thumb-shadowSets the shadow under the thumb
--slider-tick-sizeSets the width and height of each graduation mark
--slider-tick-colorSets the color of graduation marks past the current value
--slider-tick-color-activeSets the color of graduation marks at or before the current value, defaulting to the current fill color
--slider-tooltip-backgroundSets the background color of the floating value bubble
--slider-tooltip-colorSets the text color of the floating value bubble
--slider-error-colorSets the color of the error message
--slider-hint-colorSets the color of the hint message
--slider-focus-outline-colorSets the color of the focus outline drawn around the thumb

Basic

Leaving min and max unset defaults to 0100, while leaving value unset resolves to the midpoint of whatever range is in effect (50 here), exactly like a bare native <input type="range"> would.

<eun-slider name="volume" label="Volume"></eun-slider>

Min, max and step

<eun-slider
  name="temperature"
  label="Target temperature (°C)"
  min="16"
  max="28"
  step="0.5"
  value="21"
></eun-slider>

Graduated

graduated draws a row of decorative marks along the track. tick-step controls their spacing independently from step, so set it explicitly whenever step is too fine to also double as a sensible tick spacing (a step="1" slider over 0100 shouldn't draw 101 marks).

<eun-slider
  name="quality"
  label="Quality"
  min="0"
  max="100"
  step="1"
  tick-step="25"
  graduated
  value="50"
></eun-slider>

Tooltip

By default, a floating bubble mirrors the current value above the thumb while dragging or focused. Set hide-tooltip to turn it off entirely.

<eun-slider
  name="brightness"
  label="Default, drag or tab to see it"
  value="65"
></eun-slider>
<eun-slider
  name="contrast"
  label="hide-tooltip"
  hide-tooltip
  value="65"
></eun-slider>

States

Disabled

Removed from the tab order and excluded from form submission entirely.

<eun-slider name="volume" label="Disabled" value="30" disabled></eun-slider>

Readonly

Stays focusable and its value still submits, but it can't be dragged or changed by keyboard, since this needs more than the plain HTML readonly attribute on a range input (see the Accessibility tab for why).

<eun-slider name="volume" label="Readonly" value="70" readonly></eun-slider>

Hint and error

<eun-slider
  name="budget"
  label="Monthly budget ceiling"
  hint="Applies from the next billing cycle"
  min="0"
  max="2000"
  step="50"
  value="800"
></eun-slider>

Custom validators still apply normally, though a slider's own required has no effect since its value can never be "empty" (see the Accessibility tab). A business-rule validator still works exactly like on any other field:

<eun-slider
  name="seats"
  label="Seats to allocate"
  min="0"
  max="20"
  value="0"
></eun-slider>
document.querySelector("eun-slider").validators = [
  {
    isValid: (value: number) => value > 0,
    message: "Allocate at least one seat",
  },
];

Forms

Like every other Eunomia field, eun-slider is a real form-associated custom element, so it participates in a native <form>'s submission and reset.

Submit Reset
{}
<form id="my-form">
  <eun-slider name="volume" label="Volume" default-value="40"></eun-slider>
  <eun-slider
    name="brightness"
    label="Brightness"
    default-value="70"
  ></eun-slider>
  <button type="submit">Submit</button>
  <button type="reset">Reset</button>
</form>
const form = document.querySelector("#my-form");

// Fires live while dragging, exactly like the native `input` event it's
// driven by.
form.addEventListener("eunchange", () => {
  console.log(Object.fromEntries(new FormData(form)));
});

// Fires on reset (native form event), not on eunchange ; `setTimeout` lets
// the sliders' own reactive update settle first.
form.addEventListener("reset", () => {
  setTimeout(() => console.log(Object.fromEntries(new FormData(form))));
});

Custom

Override the --slider-* CSS variables, listed in full in the API tab.

<eun-slider
  class="custom-slider"
  label="Custom slider"
  value="60"
  graduated
  tick-step="20"
></eun-slider>
.custom-slider {
  --slider-track-fill-background: #be185d;
  --slider-thumb-border-color: #be185d;
  --slider-tick-color-active: #be185d;
  --slider-tooltip-background: #be185d;
  --slider-thumb-size: 22px;
}

Keyboard interactions

All native, from the underlying <input type="range">, meaning nothing here is a custom reimplementation, except blocking every key below while readonly (see "Disabled vs. readonly" further down).

Key Action
Tab / Shift+Tab Moves focus in/out of the field, natively (single tab stop)
ArrowRight / ArrowUp Increases the value by step, natively, clamped to max
ArrowLeft / ArrowDown Decreases the value by step, natively, clamped to min
Home Jumps to min, natively
End Jumps to max, natively
Page Up / Page Down Jumps by a browser-defined larger step, natively

Aria attributes

The native field is a real <input type="range">, so its implicit role="slider" and its aria-valuenow/aria-valuemin/aria-valuemax are exposed by the browser itself from value/min/max, since nothing here sets them manually. On top of that native baseline:

  • aria-invalid="true"|"false": kept in sync with valid (driven by any custom validators, and see "Required field" below for why native range/step constraints never factor in here)
  • aria-label: set from the label property directly on the native field, for the same cross-shadow-boundary reason documented on eun-counter's own Accessibility tab (aria-labelledby/for don't cross shadow-root boundaries, so a plain-text aria-label is used instead of pointing at the rendered <eun-label>)
  • aria-describedby="description": set on the field whenever a hint or error message is actually rendered below it
  • aria-readonly: reflected on the host element itself while readonly (there's no native aria-readonly equivalent the field sets on its own, since HTML readonly doesn't apply to type="range" at all, as explained below)

Label accessibility

Always set label (or slot equivalent content into the label slot), since a slider with no accessible name is one of the most disruptive screen-reader failures. State the unit where it isn't obvious from context (e.g. "Target temperature (°C)" rather than a bare "Temperature").

Disabled vs. readonly

disabled sets the native disabled attribute on the field directly: it removes the slider from the tab order, blocks every interaction, and excludes its value from form submission entirely, which is all standard native behavior, not anything custom.

readonly needed custom handling, because the HTML readonly attribute has no effect on <input type="range"> at all, since per the HTML specification, readonly only applies to text-like input types (text, number, date, ...), never to range (or checkbox/radio, for the same reason eun-switch doesn't rely on it either). eun-slider reproduces the intent of readonly instead: pointer interaction is blocked with pointer-events: none in CSS, and every keyboard interaction is blocked in script (keydown is intercepted and prevented while readonly). The field stays focusable and its value stays visible, selectable, and submitted, so it communicates "you can't change this right now", not "this doesn't apply". Prefer readonly over disabled whenever the value is still meaningful context for the user.

Required field

required is present on eun-slider for API consistency with every other field, but it's inert here: the native valueMissing check it drives elsewhere only ever fires against a genuinely empty value, and a slider's value can never be empty the way a text field's can, since connecting the component with no value/default-value set resolves it to a definite number immediately (the midpoint of min/max, exactly like a bare <input type="range"> would on its own). Use a custom validators entry instead for any "this specific value isn't acceptable" rule (see the Examples tab's "Hint and error" section).

Native range/step constraints (rangeUnderflow, rangeOverflow, stepMismatch) never surface either, for a related reason: per spec, a range input's own value sanitization algorithm silently clamps out-of-bounds or off-step values instead of flagging them invalid. That's unlike <input type="number">, which is what eun-counter relies on for its own constraint validation. eun-slider's value property mirrors that same clamping.

Tooltip and graduation marks

Both the floating value tooltip and the graduation marks are purely decorative, aria-hidden throughout: the accessible value comes from the native slider's own implicit aria-valuenow (and its live, continuous announcement while dragging with a keyboard), not from either of these. Never rely on the tooltip alone to convey the current value to someone using a screen reader.

Reference links

WAI-ARIA Authoring Practices: Slider Pattern
WAI Web Accessibility Tutorials: Labeling Controls
WHATWG HTML: The range state of the input element