Enhanced Range Input (Explainer)
- Author(s): Brecht De Ruyte
- Last updated: 2 Mar 2026
Table of Contents
- Background
- Goals
- Current State of Range Styling
- Multi-Handle Support with
<rangegroup> - Accessibility Enhancements
- Disabled State
- Progressive Enhancement
- Datalist Integration
- Multi-Color Range Segments
- Detailed Design
- Extra Examples
- Resolved Decisions
- Considerations and Open Questions
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.
- Examples from CSSWG issue
- Examples from Open UI issue
- Examples from Open UI research
- Slider parts research from Open UI
Goals
- Standardize the anatomy and structure of range inputs across browsers.
- Provide a comprehensive styling API for range inputs.
- Introduce support for multi-handle range inputs through a new
<rangegroup>element. - Enhance accessibility for range inputs, especially for multi-handle scenarios.
- 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/Blink (Chrome/Opera/Safari):
::-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 a running draft solution for this found at: https://www.w3.org/TR/css-forms-1/. 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.
A live proof-of-concept demonstrating these ideas can be found at: https://brechtdr.github.io/enhanced-range-slider-poc/
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>
<legend>Temperature Range</legend>
<label>
Minimum Temperature
<input type="range" name="temp-min" value="-25" />
</label>
<label>
Maximum Temperature
<input type="range" name="temp-max" value="15" />
</label>
</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, the <rangegroup> element is designed to work with established HTML patterns for labeling and grouping form controls.
Individual thumbs must be focusable and operable via the keyboard. The <rangegroup> approach achieves this by using actual <input type="range"> elements for each handle, preserving their native accessibility features. Users can tab between handles, and each handle maintains its standard keyboard controls (e.g., arrow keys for adjustment).
Labeling
A robust labeling strategy is crucial for users of assistive technologies. We propose a pattern that mirrors the accessible and progressively enhanced structure of <fieldset> and <legend>.
Group Label: The <rangegroup> element should be labeled by including a <legend> element as a direct child. This provides the accessible name for the entire group of sliders.
Individual Handle Labels: Each <input type="range"> within the group should have its own accessible name. This can be provided by wrapping the <input> within a <label> element (implicit labeling), which is the recommended approach. Alternatively, standard methods like an aria-label or aria-labelledby attribute on the <input> can be used.
Example Implementation
<rangegroup>
<legend>Price Range</legend>
<label>
Minimum Price
<input type="range" name="price-min" value="25" />
</label>
<label>
Maximum Price
<input type="range" name="price-max" value="75" />
</label>
</rangegroup>Focus Behavior
This approach also defines clear focus behavior.
- Clicking a
<label>will focus its associated<input>’s handle, as is standard for form controls. - The
<legend>element provides a group name but does not have a default click-to-focus behavior. This is appropriate, as there are multiple focusable targets within the group, avoiding ambiguity.
Keyboard Interaction
Each thumb within a <rangegroup> is a tab stop. Keyboard behavior for each thumb follows the existing conventions for <input type="range">:
- Tab / Shift+Tab: Moves focus between thumbs (and in/out of the group).
- Arrow Left / Arrow Down: Decreases the focused thumb’s value by one step.
- Arrow Right / Arrow Up: Increases the focused thumb’s value by one step.
- Page Down: Decreases the value by a larger step increment.
- Page Up: Increases the value by a larger step increment.
- Home: Sets the focused thumb to its minimum value.
- End: Sets the focused thumb to its maximum value.
This preserves familiarity with native range input behavior. When a <datalist> is associated, arrow keys snap to the nearest datalist value rather than stepping by a fixed increment.
Screen Reader Announcements
When a user navigates into a <rangegroup> for the first time (focusing the first thumb), the group’s overall min, max, and stepbetween values should be announced. Each individual thumb then announces its own min, max, and current value. This layered announcement model ensures users of assistive technologies understand both the group context and the specific handle they are controlling.
Disabled State
The <rangegroup> element supports the disabled attribute, following the same propagation model as <fieldset>. This provides both group-level and individual-handle control over interactivity.
Group-Level Disabling
When the disabled attribute is set on the <rangegroup> element, the entire control becomes non-interactive. All thumbs are removed from the tab order, pointer interactions on the track and thumbs are ignored, and the contained inputs are excluded from form submission.
<rangegroup disabled>
<legend>Price Range</legend>
<label>
Minimum Price
<input type="range" name="price-min" value="200" />
</label>
<label>
Maximum Price
<input type="range" name="price-max" value="800" />
</label>
</rangegroup>The track and all thumbs should be rendered in a visually distinct disabled style (e.g., reduced opacity) to communicate the non-interactive state.
Individual Thumb Disabling
The disabled attribute can also be set on individual <input type="range"> elements within a <rangegroup>. A disabled thumb is locked at its current value, removed from the tab order, and cannot be moved by pointer or keyboard. Other thumbs in the group remain fully interactive.
<rangegroup>
<legend>Price Range</legend>
<label>
Minimum Price (locked)
<input type="range" name="price-min" value="200" disabled />
</label>
<label>
Maximum Price
<input type="range" name="price-max" value="800" />
</label>
</rangegroup>All Thumbs Disabled
When every <input type="range"> within a <rangegroup> has the disabled attribute, the group should behave as if the <rangegroup> itself were disabled. The track is painted in its disabled state and there are no tab stops within the group.
State Persistence
If a <rangegroup> is disabled and later re-enabled (by removing the disabled attribute), any child inputs that were individually disabled before the group was disabled retain their own disabled state. This matches the standard behavior of <fieldset> with its child form controls.
Progressive Enhancement
This behavior aligns well with progressive enhancement. In a non-supporting browser, setting disabled on individual <input type="range"> elements works as expected with standard HTML semantics. When <rangegroup> is supported, its disabled attribute provides the additional capability of disabling all handles at once.
Progressive Enhancement
The <rangegroup> approach is designed with progressive enhancement as a core principle. In browsers that do not support the element, the markup gracefully degrades to standard, functional HTML form controls.
For example, consider a price range selector:
<rangegroup>
<legend>Price Range</legend>
<label>
Minimum Price
<input type="range" name="price-min" value="100" min="0" max="1000" />
</label>
<label>
Maximum Price
<input type="range" name="price-max" value="750" min="0" max="1000" />
</label>
</rangegroup>In a supporting browser: This renders as a single slider track with two draggable handles, labeled “Minimum Price” and “Maximum Price”, under a group heading of “Price Range”.
In a non-supporting browser: This renders as two separate, standard <input type="range"> sliders. Each slider is correctly labeled, fully functional, and accessible. The <legend> will appear as plain text, still providing context for the group of inputs that follow. This ensures the form remains usable and understandable for all users, regardless of browser support, without requiring JavaScript polyfills for basic functionality.
Design Consideration: range vs. number inputs
A consideration was raised whether <input type="number"> might serve as a better base for progressive enhancement than multiple <input type="range"> elements. While <input type="number"> provides a valid fallback for setting a range, we believe using base <input type="range"> elements better preserves the design intent. A range input is designed for selecting an approximate value within a scale, where the user is more interested in the position along the track than a precise number. This aligns with the visual metaphor of the enhanced <rangegroup> component. Using <input type="range"> as the fallback provides a more equitable, if not identical, user experience.
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">
<legend>Price Range</legend>
<label>
Minimum Price
<input type="range" name="price-min" min="0" max="50" value="20" />
</label>
<label>
Maximum Price
<input type="range" name="price-max" min="50" max="100" value="80" />
</label>
</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, enabling rich user interfaces such as budget allocators where each segment can represent a different category. Segments get numbered based on their start and end positions with 1 being at the start (based on writing mode). Example usage:
rangegroup::slider-segment(1) {
background-color: #ff5733;
}
/* Style the second segment (between the first and second handles) */
rangegroup::slider-segment(2) {
background-color: #33ff57;
}
/* Style the third segment (between the second handle and the end) */
rangegroup::slider-segment(3) {
background-color: #3357ff;
}The number of segments is determined by the number of thumbs.
Detailed Design
HTML Attributes
<rangegroup> Attributes
min: Specifies the minimum value for the entire range group, defining the lower bound of the track.max: Specifies the maximum value for the entire range group, defining the upper bound of the track.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.disabled: When present, disables the entire range group. All contained handles become non-interactive, are removed from the tab order, and their values are excluded from form submission. This mirrors the behavior of thedisabledattribute on<fieldset>.
<input type="range"> Attributes (within a <rangegroup>)
These attributes function as they normally would, but their values are constrained by the parent <rangegroup>.
min: The minimum value for this specific handle. Its effective range is determined by its interaction with the parent<rangegroup>.max: The maximum value for this specific handle. Its effective range is determined by its interaction with the parent<rangegroup>.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.disabled: Disables this specific handle. The thumb is locked at its current value, removed from the tab order, and excluded from pointer interactions. Other handles in the group remain interactive. When all child inputs are disabled, the group behaves as if it were disabled as a whole.
min and max Attribute Interaction
The <rangegroup> element and its child <input type="range"> elements can both have min and max attributes. Their interaction is key to providing both flexibility and a coherent progressive enhancement story.
<rangegroup>minandmax: These attributes define the overall coordinate space of the slider. They determine the full visual range of the track.<input>minandmax: These attributes define the allowable value range for that specific handle.
The effective range for any given handle is the intersection of its own constraints and the parent <rangegroup>’s constraints. The tightest constraint always wins. In other words, a handle’s value must be greater than or equal to both its own min and the group’s min, and less than or equal to both its own max and the group’s max.
- If a handle’s range is within the group’s range (e.g.,
rangegroup max="100",input max="70"): The track will render from 0 to 100, but the handle will be prevented from moving past 70. This is a desirable pattern for use cases like a seek bar for a live video stream, where the total buffer is visible but the user cannot seek into the future. - If a handle’s range exceeds the group’s range (e.g.,
rangegroup max="100",input max="200"): The handle’s movement will be clamped by the<rangegroup>’s bounds. The handle will not be able to exceed a value of 100. This ensures the component’s visual representation is the source of truth and prevents invalid states.
This model supports progressive enhancement. In a non-supporting browser, the individual inputs with their min and max attributes function correctly as standalone sliders. When <rangegroup> is supported, it unifies the UI and can apply overarching constraints without breaking the underlying inputs.
Track Click Behavior
To provide an intuitive single-pointer interaction model, the <rangegroup> component’s track-clicking behavior mirrors that of the native <input type="range">.
When a user clicks or taps anywhere on the slider’s track, the component identifies the thumb closest to the click location and moves it to that position.
This approach offers several benefits:
- Familiarity: It aligns with user expectations based on the standard behavior of single-handle sliders and some popular component libraries.
- Direct Manipulation: It provides a quick and direct way for users to move any handle to a desired point with a single click, without needing to drag.
Disabled thumbs are excluded from the closest-thumb calculation. If only one thumb is active and the other is disabled, a track click will always move the active thumb.
While this interaction is powerful, it’s worth noting that on touch devices with coarse pointers, care must be taken to avoid accidental adjustments. However, we believe the benefit of adhering to a well-established browser-native pattern provides the most valuable and predictable user experience.
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.
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(1) {
background-color: #ff5733;
}
rangegroup::slider-segment(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 group.
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 forquerySelectorAllgetRangeInput(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">
<legend>Price Range</legend>
<label>
Minimum Price
<input type="range" name="price-min" min="0" max="500" value="250" step="10" />
</label>
<label>
Maximum Price
<input type="range" name="price-max" min="500" max="1000" value="750" step="10" />
</label>
</rangegroup>rangegroup {
appearance: base;
width: 300px;
}
rangegroup::slider-track {
height: 6px;
background-color: #f0f0f0;
}
rangegroup::slider-segment(1) {
background-color: #ddd;
}
rangegroup::slider-segment(2) {
background-color: #4caf50;
}
rangegroup::slider-segment(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">
<legend>Temperature Range</legend>
<label>
Low
<input type="range" name="temp-low" min="-100" max="-25" value="-50" />
</label>
<label>
Medium
<input type="range" name="temp-medium" min="-25" max="25" value="0" />
</label>
<label>
High
<input type="range" name="temp-high" min="25" max="100" value="75" />
</label>
</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(1) {
background-color: #ff5733;
}
rangegroup::slider-segment(2) {
background-color: #33ff57;
}
rangegroup::slider-segment(3) {
background-color: #3357ff;
}
rangegroup::slider-segment(4) {
background-color: #ff33a8;
}Resolved Decisions
The following questions have been discussed and resolved in Open UI telecons:
- Handle count limits (#1337): No limit is imposed on the number of range inputs in a
<rangegroup>. It is the author’s responsibility to determine the appropriate number for their use case. Community feedback is being gathered on the prevalence of 3+ thumb use cases to determine whether a simpler two-thumb attribute API should coexist with the N-thumb<rangegroup>approach. - Keyboard navigation (#1185): Each thumb gets a tab stop. Arrow keys, Page Up/Down, Home, and End behave as they do on a standard
<input type="range">. See the Keyboard Interaction section. - Disabled attribute propagation (#1338):
disabledon<rangegroup>cascades to all thumbs. Individual thumbs can be disabled independently. When all thumbs are disabled, the group behaves as fully disabled. See the Disabled State section. - Screen reader announcements (#1339): The group’s min, max, and stepbetween should be announced when entering the group (first thumb). Each thumb then announces its own min, max, and current value. See the Screen Reader Announcements section.
- Naming convention (#1197): All pseudo-elements use the
slider-prefix (e.g.,::slider-track,::slider-thumb), following the CSSWG decision in the CSS Forms specification.
Considerations and Open Questions
- Thumb collision handling (#1184): How should overlapping thumbs behave? Current consensus leans toward allowing overlap for the initial version, with the option to refine behavior based on real-world usage. Authors can prevent overlap using
stepbetweenor per-thumbmin/maxconstraints. The UX of selecting overlapping thumbs (e.g., disambiguating via drag direction) remains an area for further work. - Track click behavior (#1278): The current proposal moves the closest thumb to the click position. Alternative approaches (e.g., only moving thumbs when clicking outside the selected range, or no track-click on touch devices) have been suggested and remain under discussion.
- Vertical orientation: 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.
- Automatic tick mark generation: Should we provide options for generating tick marks without requiring a
<datalist>(e.g., evenly spaced ticks based on astepattribute on the rangegroup)? - Tick mark legibility: 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?
- Programmatic segment styling: Should we provide a way to set segment colors via a JavaScript API?
- Programmatic constraint violations: What should the behavior be when a handle’s value is updated programmatically to a value that would violate
stepbetweenor other range limits? - Form submission: How should form submission work for
<rangegroup>elements? Each child<input>submits its own name/value pair individually, as standard form controls. Should the<rangegroup>also support a group-level submission format?
Open UI