Skip to content
Open UI

Enhanced Range Input (Explainer)

Table of Contents

Background

The <input type="range"> element has long been a part of HTML forms, providing a slider control for selecting numeric values. However, its current implementation lacks flexibility in styling and functionality, leading developers to often resort to custom implementations. These custom solutions can result in reduced performance, reliability, and accessibility compared to native form controls.

The web development community has expressed a need for more customizable and feature-rich range inputs, particularly for use cases such as price range selectors, date/time range pickers, and other multi-value selection scenarios.

Goals

  1. Standardize the anatomy and structure of range inputs across browsers.
  2. Provide a comprehensive styling API for range inputs.
  3. Introduce support for multi-handle range inputs through a new <rangegroup> element.
  4. Enhance accessibility for range inputs, especially for multi-handle scenarios.
  5. Improve the overall user experience and developer ergonomics for range inputs.

Current State of Range Styling

Currently, the state of styling <input type="range"> across browsers is inconsistent and challenging. Different browsers use their own pseudo-elements for styling range inputs, leading to a fragmented landscape for developers. Here’s an overview of the current state:

::-webkit-slider-thumb { /* Styles the thumb */ }
::-webkit-slider-runnable-track { /* Styles the track */ }

Firefox:

::-moz-range-thumb { /* Styles the thumb */ }
::-moz-range-track { /* Styles the track */ }
::-moz-range-progress { /* Styles the progress/fill below the thumb */ }

IE/Edge:

::-ms-thumb { /* Styles the thumb */ }
::-ms-track { /* Styles the track */ }
::-ms-fill-lower { /* Styles the progress/fill below the thumb */ }
::-ms-fill-upper { /* Styles the fill above the thumb */ }

This fragmentation makes it difficult for developers to create consistent range input styles across browsers, often requiring browser-specific code or fallbacks.

There currently is running a draft solution for this found at: (https://drafts.csswg.org/css-forms/)[https://drafts.csswg.org/css-forms/]. We would at least like to maintain the same naming convention:

  • ::slider-track: The main track along which the thumb(s) move.
  • ::slider-fill: The filled portion of the track, typically between the minimum value and the thumb (or between thumbs for multi-handle ranges).
  • ::slider-thumb: The draggable handle(s) used to select values.

Taking this further, we would like to propose new pseudo elements following the same convention:

  • ::slider-segment: Sections of the track between handles in multi-handle ranges.
  • ::slider-tick: Optional tick marks along the track for value representation. (when datalist is paired.)
  • ::slider-tick-label: Labels associated with tick marks. (when datalist is paired.)

This would follow the idea of the appearance: base value from the CSS Forms specification:

input[type="range"] {
    appearance: base;
}

This opt-in approach allows developers to access enhanced styling capabilities while maintaining a partial backwards compatibility.

Multi-Handle Support with <rangegroup>

Based on community feedback and discussions, we propose a new approach for multi-handle functionality. Instead of adding a new attribute to the existing <input type="range"> element or adding a new type, we believe a new <rangegroup> element could offer us the correct flexibility. This would be a wrapper element that can contain multiple <input type="range"> elements. This approach offers several advantages:

  • Easy feature detection
  • Better progressive enhancement as browsers that don’t support <rangegroup> will still display individual range inputs
  • Clearer separation of concerns between individual range handles
  • More intuitive form submission with distinct name/value pairs
  • Simplified styling and attribute management

Example usage:

<rangegroup name="temperature-range" min="-100" max="100">
  <input type="range" name="temperature-range-min" min="-50" max="0" value="-25">
  <input type="range" name="temperature-range-max" min="0" max="20" value="15">
</rangegroup>

The <rangegroup> element would visually present these as a single slider with multiple handles on a common track, while maintaining the individual inputs for form submission and JavaScript interaction.

Accessibility Enhancements

To improve accessibility, especially for multi-handle ranges, we propose making individual thumbs focusable and providing clear audio cues for screen readers. The <rangegroup> approach naturally supports this by preserving the individual input elements with their native accessibility features. For keyboard navigation, users can tab between individual range inputs within a <rangegroup>, and each input maintains its standard keyboard controls (arrow keys for adjustment).

Labeling

A <rangegroup> can have an associated label by referencing the id and use the for attribute on the label.

Progressive Enhancement

The <rangegroup> approach provides natural progressive enhancement. Browsers that do not support the new element will display the individual range inputs as separate controls, maintaining basic functionality.For example, in a non-supporting browser, this code:

<rangegroup name="price-range" min="0" max="1000">
    <input type="range" name="price-min" min="0" max="500" value="100">
    <input type="range" name="price-max" min="500" max="1000" value="750">
</rangegroup>

Would render as two separate range inputs, still allowing users to select minimum and maximum values, but without the unified visual presentation.

Datalist Integration

We propose standardizing and enhancing the integration of <datalist> with range inputs and rangegroups. This will allow for consistent implementation of tick marks and predefined values across browsers. For <rangegroup> elements, the <datalist> can be associated with the group as a whole, and all contained range inputs will share the same tick marks:

<rangegroup name="price-range" min="0" max="100" list="price-ticks">
    <input type="range" name="price-min" min="0" max="50" value="20">
    <input type="range" name="price-max" min="50" max="100" value="80">
</rangegroup>
<datalist id="price-ticks">
    <option value="0">$0</option>
    <option value="25">$25</option>
    <option value="50">$50</option>
    <option value="75">$75</option>
    <option value="100">$100</option>
</datalist>

Alternatively, datalist could be provided on each input as well. While these won’t render inside of the <rangegroup>, they would render on individual inputs when there is no support for the feature.

Multi-Color Range Segments

To support different colors between handles in a <rangegroup>, we introduce the ::slider-segment pseudo-element. This allows for granular control over the appearance of each segment in the range.Example usage:

rangegroup::slider-segment:nth-child(1) {
    background-color: #FF5733;
}

/* Style the second segment (between the first and second handles) */
rangegroup::slider-segment:nth-child(2) {
    background-color: #33FF57;
}

/* Style the third segment (between the second handle and the end) */
rangegroup::slider-segment:nth-child(3) {
    background-color: #3357FF;
}

The amount of segments get crated based on the amount of thumbs.

Detailed Design

HTML Attributes

<rangegroup> Attributes

  • min: Specifies the minimum value for the entire range group overruling the children’s attribute.
  • max: Specifies the maximum value for the entire range group overruling the children’s attribute.
  • name: The name of the range group, used for identification.
  • list: Links the range group to a <datalist> element, providing tick marks or predefined values.
  • stepbetween: Defines the minimum distance between handles in a range group.
<rangegroup name="price-range" min="0" max="1000" stepbetween="50" list="price-ticks">
    <input type="range" name="price-min" min="0" max="500" value="100">
    <input type="range" name="price-max" min="500" max="1000" value="750">
</rangegroup>

<input type="range"> Attributes (within a <rangegroup>)

In general, everything is still possible for individual inputs inside of the <rangegroup>. The following specifies how they would work together with the new wrapper element.

  • min: The minimum value for this specific handle.
  • max: The maximum value for this specific handle.
  • value: The current value of this handle. This still updates individually by changing the thumb
  • name: The name of this specific range input for form submission.
  • step: The step increment for this specific handle. This results in the steps of the thumb in a rangegroup environment, meaning that each thumb could potentially have different steps.

CSS Properties and Pseudo-elements

To address the current fragmentation and provide a unified styling API, we propose the following pseudo-elements, aligning with the CSS Forms specification:

  • ::slider-track: Represents the main track of the range group.
  • ::slider-segment: Represents sections of the track between handles.
  • ::slider-thumb: Represents the draggable handles within the group.
  • ::slider-tick: Represents individual tick marks on the range group with attached datalist.
  • ::slider-tick-label: Represents the label associated with each tick mark of a datalist option.

These pseudo-elements can be used in combination with other CSS selectors, such as :nth-child(), to provide granular control over individual elements.

Example usage:

/* Styling a basic range input */
input[type="range"] {
    appearance: base;
}

input[type="range"]::slider-track {
    height: 4px;
    background-color: #ddd;
}

input[type="range"]::slider-fill {
    background-color: #4CAF50;
}

input[type="range"]::slider-thumb {
    width: 20px;
    height: 20px;
    background-color: #2196F3;
    border-radius: 50%;
}

rangegroup {
    appearance: base;
}

rangegroup::slider-track {
    height: 6px;
    background-color: #f0f0f0;
}

rangegroup::slider-segment:nth-child(1) {
    background-color: #FF5733;
}

rangegroup::slider-segment:nth-child(2) {
    background-color: #33FF57;
}

rangegroup input[type="range"]::slider-thumb {
    width: 24px;
    height: 24px;
    background-color: #2196F3;
    border-radius: 50%;
}

JavaScript API

The existing JavaScript API for <input type="range"> remains unchanged inside of the gorup.

For groups specifically we’d like to introduce a few new ones:

  • values: A property that returns an array of values for all contained range inputs.
  • inputs: A property that returns a collection of the contained range input elements. A shorthand for querySelectorAll
  • getRangeInput(index): Returns the range input at the specified index.
  • setRangeValue(index, value): Sets the value for a specific handle.

Example usage:

const rangeGroup = document.querySelector('rangegroup[name="price-range"]');

// Getting values
console.log(rangeGroup.values); // [100, 750]

// Getting inputs
const inputs = rangeGroup.inputs;
console.log(inputs.length); // 2

// Setting a specific handle's value
rangeGroup.setRangeValue(0, 150);
console.log(rangeGroup.values); // [150, 750]

Extra Examples

Dual-Handle Range Group

 <rangegroup name="price-range" min="0" max="1000">
    <input type="range" name="price-min" min="0" max="500" value="250" step="10">
    <input type="range" name="price-max" min="500" max="1000" value="750" step="10">
</rangegroup>
rangegroup {
    appearance: base;
    width: 300px;
}

rangegroup::slider-track {
    height: 6px;
    background-color: #f0f0f0;
}

rangegroup::slider-segment:nth-child(1) {
    background-color: #ddd;
}

rangegroup::slider-segment:nth-child(2) {
    background-color: #4CAF50;
}

rangegroup::slider-segment:nth-child(3) {
    background-color: #ddd;
}
 rangegroup input[type="range"]::slider-thumb {
    width: 24px;
    height: 24px;
    background-color: #2196F3;
    border-radius: 50%;
}

Multi-Handle Range Group with Colored Segments

<rangegroup name="temperature-range" min="-100" max="100">
    <input type="range" name="temp-low" min="-100" max="-25" value="-50">
    <input type="range" name="temp-medium" min="-25" max="25" value="0">
    <input type="range" name="temp-high" min="25" max="100" value="75">
</rangegroup>
rangegroup {
    appearance: base;
    width: 300px;
    height: 20px;
}

rangegroup::slider-track {
    height: 10px;
    background-color: #ddd;
}

rangegroup input[type="range"]::slider-thumb {
    width: 20px;
    height: 20px;
    background-color: #2196F3;
    border-radius: 50%;
}

rangegroup::slider-segment:nth-child(1) {
    background-color: #FF5733;
}

rangegroup::slider-segment:nth-child(2) {
    background-color: #33FF57;
}

rangegroup::slider-segment:nth-child(3) {
    background-color: #3357FF;
}

rangegroup::slider-segment:nth-child(4) {
    background-color: #FF33A8;
}

Considerations and Open Questions

  • Should there be a limit on the number of range inputs that can be included in a <rangegroup>? Based on discussions, we’ve decided not to impose a limit, leaving it to authors to determine the appropriate number for their use case. A separate issue will be created to address potential accessibility concerns with a large number of handles.
  • How should we handle thumb collision in multi-handle ranges?
  • What is the best way to handle keyboard navigation for multi-handle ranges? While tabbing between inputs is natural, should there be additional keyboard shortcuts for more efficient navigation?
  • Should we provide built-in support for non-linear scales (e.g., logarithmic)?
  • Should we consider additional pseudo-elements for more granular styling control?
  • How should we handle vertical orientation for range inputs? The CSS Forms specification introduces a slider-orientation property that could be adopted for this purpose.
  • Should we adopt the ::slider-* naming convention from the CSS Forms specification for all pseudo-elements, or keep some with the ::range-* prefix to distinguish between standard range functionality and the multi-handle extensions?
  • How should we handle the positioning and spacing of tick marks when using <datalist> with <rangegroup> elements?
  • Should we provide options for automatic tick mark generation (e.g., evenly spaced) without requiring a <datalist>? These could be auto-generated based on a step attribute of a rangegroup
  • How can we ensure that tick marks and labels remain legible and usable on small-screen devices or with a large number of ticks? Is this author responsibility?
  • How should we handle color transitions between segments in multi-handle ranges? Should there be an option for gradient transitions? Maybe grouping of segments?
  • Should we provide a way to programmatically set segment colors with a JavaScript API?
  • How should the individual range inputs’ min and max attributes interact with the parent <rangegroup>’s min and max attributes, should they overrule?
  • What should the behavior be when a handle’s value is updated programmatically to a value that would violate the constraints imposed by stepbetween or other range limits?
  • How should form submission work for <rangegroup> elements? Should they submit as a single value (comma-separated) or as multiple values with the same name?