Enhanced Range Input (Explainer)
- Authors
- @utilitybend
- Created
- Last Updated
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 a
<rangegroup>element that composites one or more<input type="range">handles onto a shared track. - 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/EdgeHTML (historical):
These pseudo-elements were only ever implemented by Internet Explorer and pre-Chromium (EdgeHTML) Edge. Current Edge is Chromium-based and uses the WebKit/Blink pseudo-elements above. They are listed here because they are the origin of the “upper/lower fill” split that later became ::slider-fill-over / ::slider-fill-under.
::-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. The one standardized escape hatch today is accent-color, which tints the native control without vendor pseudo-elements, but it is a single colour, not a parts API.
The running draft solution for this is CSS Form Control Styling Module Level 1. Note that the dated TR snapshot still documents the older ::track / ::fill / ::thumb names; the CSSWG resolved to keep the slider- prefixes in csswg-drafts#9830, so the editor’s draft is the reference for this explainer. 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, between the start of the track and the thumb.::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. These are additions proposed by this explainer, not part of CSS Forms Level 1:
::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.)
Relationship to ::slider-fill-over and ::slider-fill-under
The CSSWG resolved on 19 February 2026 to add ::slider-fill-over and ::slider-fill-under, keeping ::slider-fill as an alias for ::slider-fill-under. That resolution is not yet reflected in the editor’s draft. It was raised in that discussion that upper/lower naming needs to stay compatible with this multi-handle proposal.
The two models line up as follows:
- One thumb:
::slider-fill-underis the portion below the value and::slider-fill-overthe portion above it. These are equivalent to::slider-segment(1)and::slider-segment(2). - Two or more thumbs: “under” and “over” no longer identify a single region, so
::slider-segment(n)is the addressing mechanism.::slider-fillis not defined as spanning the space between thumbs.
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 <rangegroup> element rather than a new attribute or a second range input type. It is a wrapper that contains one or more <input type="range"> elements and paints them as a single slider on a shared track.
A group of one is valid. The composite <rangegroup> UI is still shown, with a single thumb on the group track. It is not a no-op wrapper and must not hide the control. Authors should not need a second native element for “enhanced single thumb” versus “multi-thumb.” With one child, stepbetween has no effect.
A single-thumb group is also the point where this proposal meets a plain <input type="range"> with appearance: base: it has one thumb, one filled portion, and one unfilled portion. Everything the CSS Forms specification defines for a single range control therefore still applies, including ::slider-fill (and, once specified, ::slider-fill-under / ::slider-fill-over). The group only adds the shared track coordinate space, group-level min / max / list, and the group label.
A live proof-of-concept demonstrating these ideas can be found at: https://brechtdr.github.io/enhanced-range-slider-poc/ (including a group of one).
This approach offers several advantages:
- Easy feature detection
- Better progressive enhancement as browsers that don’t support
<rangegroup>will still display the child range input(s) - Clearer separation of concerns between individual range handles when there is more than one
- More intuitive form submission with distinct name/value pairs
- Simplified styling and attribute management
- One element type that scales from a single handle to N handles
Example usage (two handles):
<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>
Example usage (one handle):
<rangegroup min="0" max="100">
<legend>Volume</legend>
<label>
Volume
<input type="range" name="volume" value="40" />
</label>
</rangegroup>
In a supporting browser this still renders as the <rangegroup> slider (one thumb on the group track), not as a hidden wrapper. In a non-supporting browser it is a labeled <input type="range"> as today.
The <rangegroup> element presents child ranges as handles on a common track, while maintaining the individual inputs for form submission and JavaScript interaction.
Form Association and Naming
<rangegroup> follows the model of <fieldset>: it is a listed, form-associated element, but it is not a submittable element.
nameon<rangegroup>identifies the group in theform.elementsAPI (anddocument.getElementsByName). It does not itself contribute an entry to the submitted entry list, exactly asnameon<fieldset>does not.nameon each child<input type="range">is what gets submitted, as a normal name/value pair. Disabled handles, and all handles when the group is disabled, are excluded.formmay be specified on<rangegroup>to set an explicit form owner, matching<fieldset>.
<form>
<rangegroup name="price-range">
<legend>Price Range</legend>
<label>Minimum Price <input type="range" name="price-min" value="100" /></label>
<label>Maximum Price <input type="range" name="price-max" value="750" /></label>
</rangegroup>
</form>
Submitting this form sends price-min=100&price-max=750. There is no price-range entry. Scripts can still reach the group with form.elements["price-range"].
Conventions such as price-min / price-max, or array-style names like price[], are author choices. The platform assigns no meaning to them. This keeps the submitted payload identical whether or not the browser supports <rangegroup>, which is the core of the progressive enhancement story.
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 its first child. This provides the accessible name for the entire group of sliders.
This requires a change to HTML. The content model of <legend> currently allows it only as the first child of <fieldset> or of <optgroup>; the <optgroup> context is itself a recent addition made for customizable <select>. We propose extending it the same way, to allow <legend> as the first child of <rangegroup>. As with <fieldset>, only the first <legend> child names the group.
Until that change lands, a <legend> inside <rangegroup> is invalid markup and, in a non-supporting browser, is not associated with anything. Authors who need markup that validates today can wrap the group in a <fieldset> and put the <legend> there instead; the group is then named through aria-labelledby on the <rangegroup>. The examples in this explainer use the proposed form.
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. With more than one thumb that avoids ambiguity about which handle to focus. With a single thumb the same rule applies, matching<fieldset>/<legend>rather than forwarding clicks to the only input.
Keyboard Interaction
Each thumb within a <rangegroup> is a tab stop (one stop when the group has a single child). 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 and matches the keyboard contract expected of the slider role.
When a <datalist> is associated, we propose that arrow keys move to the adjacent datalist value rather than stepping by a fixed increment. This is new behavior, not existing behavior: on <input type="range"> today, a <datalist> only renders tick marks and pointer-snapping, while keyboard stepping continues to follow step. The proof-of-concept implements the proposed keyboard snapping. Whether this belongs on <rangegroup> only, or on any range with a list attribute, is worth resolving with the HTML side.
Whether a draggable interval would also be a tab stop is tracked in #1511.
Screen Reader Announcements
When a user navigates into a <rangegroup> for the first time (focusing the first thumb), the group’s overall min and max should be announced, plus stepbetween when it applies (two or more thumbs). 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.
Legend Contents
<fieldset disabled> deliberately does not disable form controls inside its first <legend>, which is what makes the “checkbox in the legend enables the fieldset” pattern work. Because <rangegroup> is expected to contain only range handles, we propose the simpler rule: the <legend> of a <rangegroup> should not contain form controls. If it does, matching the <fieldset> exception is the safer default.
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”. A <rangegroup> with one child range likewise still shows that unified slider (one thumb), not a collapsed or hidden control.
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.
Today the list attribute exists only on <input>; allowing it on <rangegroup> is part of this proposal. 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>
Note that list on the group alone does not degrade: in a non-supporting browser the attribute is ignored and the fallback sliders show no tick marks. Authors who want ticks in both cases should also set list on each child input. The group-level value takes precedence when <rangegroup> is supported, so the two can coexist:
<rangegroup min="0" max="100" list="price-ticks">
<legend>Price Range</legend>
<label>Minimum Price <input type="range" name="price-min" value="20" list="price-ticks" /></label>
<label>Maximum Price <input type="range" name="price-max" value="80" list="price-ticks" /></label>
</rangegroup>
Whether the group should instead inherit list down to its children automatically, so the duplication is unnecessary, is an open question.
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. With one thumb there are two segments (track start to thumb, and thumb to track end), matching the ::slider-fill-under / ::slider-fill-over split resolved for a single range. With two thumbs there are three segments (before the first handle, between handles, and after the last handle), and so on.
Custom Track Shapes
A frequent request from authors is the ability to render range inputs along non-linear tracks: gently curved or wavy tracks, arcs, and full circular “knob” controls. Today this forces authors to abandon the native control entirely and rebuild it from scratch, losing accessibility, form integration, and keyboard behavior in the process.
Rather than introducing a dedicated shape attribute or a family of shape-specific pseudo-elements, we propose a smaller, more composable primitive: the user agent exposes each thumb’s current fractional position along the track as a CSS custom property on the ::slider-thumb pseudo-element. Authors can then place thumbs along an arbitrary path using the existing CSS offset-path / offset-distance mechanism, with no new layout model and no JavaScript.
We propose the property name --slider-thumb-position, resolving to a <percentage> from 0% (the start of the track, per writing mode) to 100% (the end). Because it is a percentage, it can be passed directly to offset-distance, which interprets a percentage as a fraction of the total path length.
The custom-property spelling is a placeholder and needs CSSWG input. A user-agent-defined --* property is unusual: custom properties are author space, so nothing stops an author from overriding it and desynchronising the thumb from its value. A registered property, or a dedicated non-custom property on the pseudo-element, may be a better shape for the same primitive.
Wavy track
input[type="range"] {
appearance: base;
}
/* Paint the track as a wave (e.g. an SVG mask or background) */
input[type="range"]::slider-track {
background: url("wave.svg") center / 100% 100% no-repeat;
}
/* Ride each thumb along the same curve */
input[type="range"]::slider-thumb {
offset-path: path("M 0,20 C 40,0 60,40 100,20");
offset-distance: var(--slider-thumb-position);
}
Circular track
A circular control is the same technique with a closed path. A single-thumb circular input behaves as a rotary knob; multi-thumb <rangegroup> controls become radial range selectors.
rangegroup {
appearance: base;
inline-size: 240px;
aspect-ratio: 1;
}
rangegroup::slider-thumb {
offset-path: path("M 120,20 A 100,100 0 1 1 119.99,20");
offset-distance: var(--slider-thumb-position);
}
Fills and segments
The ::slider-fill and ::slider-segment pseudo-elements continue to expose the start and width of each segment so that authors can repaint them to match the chosen shape, for example as a conic-gradient ring for circular tracks or an SVG stroke for wavy ones. The same fractional-position primitive could be exposed for segment boundaries to make shaped fills fully declarative.
Accessibility
Custom track shapes are purely presentational. Thumbs still move monotonically from the track’s minimum to its maximum as --slider-thumb-position advances from 0% to 100%, so keyboard interaction, value semantics, and screen reader announcements are identical to a linear slider. Authors take on responsibility for ensuring the rendered shape keeps thumbs and ticks legible and hit-targetable.
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 in theform.elementsAPI. It is not submitted, matchingnameon<fieldset>. See Form Association and Naming.form: Associates the range group with a form element explicitly, matchingformon<fieldset>. Like<fieldset>, it does not propagate to descendant controls; a detached group’s child ranges still need their ownformattributes to submit to that form.list: Links the range group to a<datalist>element, providing tick marks or predefined values. This extendslist, which today is only valid on<input>.stepbetween: Defines the minimum distance between adjacent handles. It is a single value on the group, not a per-gap list. It has no effect when the group contains fewer than two handles. A maximum distance between handles is intentionally not in v1 (see Resolved Decisions).interaction: Working name — needs bikeshed (#1460). Not a committed HTML attribute; do not usedraggable. Keywords:endpoints(default) andrange. See Track Click Behavior. Other names that have been floated:selects,pointermode,rangemode,adjust. Watch collisions withinputmodeand with the CSSinteractivityproperty, which controls inertness and is a near-miss for this name.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, used for form submission and in theform.elementsAPI. This is the only name that appears in submitted form data.step: The step increment for this specific handle. Each thumb may use a differentstep. That is independent ofstepbetween, which remains one group-level minimum gap and does not vary between thumb pairs.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 interactive, a track click moves that thumb.
This is the endpoint-oriented default (interaction="endpoints"). A range-oriented keyword (interaction="range") is proposed as a working API for calendar blocks and video in/out: dragging a segment translates the two thumbs bounding it, preserving its width; a press between thumbs does not pick a nearest thumb; a press outside any segment (before the first thumb or after the last) still moves the closest thumb.
The attribute name and its keywords are a working name and need bikeshed on #1460 (not a second issue). interaction is close to the CSS interactivity property and to inputmode, so a different name may well win. draggable is rejected because it collides with the existing global attribute. Nothing in this section depends on the final spelling: the two behaviors are what matter. Related: #1278. Keyboard and focus for a draggable segment: #1511.
The Lit proof-of-concept implements these keywords for any thumb count. With three or more thumbs the pointer position picks the pair: pressing inside a segment names the two thumbs that bound it, so there is no need to decide up front which interval is “the” interval. A dragged segment is clamped by the thumbs on either side of it, by any per-thumb min/max, and by stepbetween, so it stops rather than squashing. Segments whose bounding thumbs are not both enabled are not draggable. A library vs in-app survey is on #1460: libraries default to nearest-thumb and offer interval-drag as opt-in, which supports endpoints as the default if a mode ships.
Multitouch (two fingers independently dragging two thumbs) is not supported. A two-finger gesture is pinch-zoom, as with other widgets (#1461).
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, ::slider-fill, and ::slider-thumb come from that specification; ::slider-segment, ::slider-tick, and ::slider-tick-label are proposed here.
::slider-track: Represents the main track of the range group.::slider-fill: Represents the filled portion of a single-thumb control. With two or more thumbs, use::slider-segment.::slider-segment: Represents sections of the track between handles.::slider-thumb: Represents the draggable handles within the group. On<rangegroup>, the group owns the shared visual anatomy, sorangegroup::slider-thumbstyles all thumbs; the child inputs remain the semantic and form controls.::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::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.
Potential future additions (deferred from v1):
addThumb(value, options?): Adds a new handle to the group at the givenvalueand returns the created<input type="range">. Because handles are backed by real<input type="range">elements, this is a convenience over manually creating and appending an input. The optionaloptionsobject acceptsmin,max,name, andlabel. Use cases such as a gradient editor that lets users add and remove color stops on demand are a primary motivation for this method.removeThumb(index): Removes the handle (and its backing<input>) at the specified index.
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]
Because each handle is a real <input type="range"> in the light DOM, authors can add or remove handles by appending or removing inputs directly. addThumb/removeThumb are proposed as a future ergonomic shorthand that would keep value normalization and event dispatch consistent.
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::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::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 items have been discussed in Open UI telecons. GitHub issues may remain open for tracking; the bullets below are the current explainer consensus.
- Handle count (#1337, #1183): A
<rangegroup>may contain one or more range inputs. A group of one still renders the composite slider. There is no upper limit; authors choose how many thumbs their use case needs. Whether 3+ thumb patterns are common enough to also ship a simpler two-thumb-only API is still community feedback, not a cap on<rangegroup>. - 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 and max should be announced when entering the group (first thumb), plus
stepbetweenwhen it applies. 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 resolution on csswg-drafts#9830 to keep the prefixes. The editor’s draft reflects this; the dated TR snapshot does not. - Group naming and form submission:
<rangegroup>is a listed, form-associated element but not a submittable one. Groupnameis for theform.elementsAPI only; child inputs submit their own name/value pairs. This mirrors<fieldset>. See Form Association and Naming. - Per-pair
stepbetween(#1463, telecon 27 Aug 2026): v1 uses onestepbetweenon the group. Different minimum gaps between different thumb pairs are deferred until there are concrete use cases. - Maximum space between thumbs (#1462, telecon 27 Aug 2026): Out of v1. A smaller first API is preferred; a maximum-gap attribute can be added later with no web-compat cost.
- Multitouch dual-thumb drag (#1461, telecon 27 Aug 2026): Resolved: do not support a multitouch gesture that moves two sliders at once by dragging two thumbs. Two-finger input is pinch-zoom. Translating an interval with one pointer is #1460, not v1 by default.
- Linked number (or other) fields (#1459, telecon 27 Aug 2026): Out of this explainer. Value linking should work for a single
input type=rangeas well as<rangegroup>, and belongs with a general data-binding / invoker discussion rather than a rangegroup-onlyreferenceattribute or a built-in sibling number input.
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 vs segment drag (#1278, #1460): Default remains closest-thumb (
interaction="endpoints"). Range-oriented UIs want to translate the selected interval. Working nameinteractionwith keywordsendpoints|rangeis in this explainer and the PoC; the name and keywords need bikeshed (keep that discussion on #1460, not a new issue). Rejected:draggable. Default is still not a formal resolution; a survey comment on #1460 found library defaults matchendpoints, with interval-drag as opt-in. With three or more thumbs, “the” segment used to look ambiguous; pointer position resolves it, since grabbing a segment names the pair of thumbs that bound it, and outer regions keep nearest-thumb (the PoC now works for any thumb count, clamping a dragged segment against its neighbours andstepbetween). What stays unresolved is reaching an interval without a pointer: #1511. - Keyboard and focus for a draggable segment (#1511, split from #1460): If range-oriented dragging ships, should the segment be a tab stop / AT object, or is keyboard-only movement of each thumb enough?
- Vertical orientation: How should we handle vertical orientation for range inputs? The CSS Forms specification introduces a
slider-orientationproperty that could be adopted for this purpose, but that section is marked “Rework this property” in the editor’s draft and open questions remain about howautoresolves against writing mode (csswg-drafts#11891). Browsers also disagree today on whether arrow keys and Home/End invert on a vertical slider, so this cannot be treated as settled. - 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? This extends toaddThumb: when a handle is added at a value that violatesstepbetweenor overlaps an existing handle, should it be clamped to the nearest valid position, rejected, or inserted as-is? - Custom track shapes and assistive technology: Non-linear tracks (wavy, circular) reorder thumbs visually without changing their value semantics. Is the
--slider-thumb-positionprimitive plusoffset-pathsufficient for authors, or should the platform offer higher-level shape affordances? How should hit-testing, focus rings, and tick placement adapt on a curved or closed path? - Group-level submission format: v1 submits only child name/value pairs, as described in Form Association and Naming. A group-level convenience (for example, a single combined entry) is deliberately out of scope: submitting both the group and its children would change the payload depending on browser support and break the progressive enhancement story. It can be added later without web-compat cost if concrete use cases appear.
- Datalist keyboard snapping: We propose that arrow keys move between datalist values when
listis present. Native<input type="range">does not do this today. Should this behavior be scoped to<rangegroup>, or specified for any range input with alistattribute? --slider-thumb-positionshape: Should the thumb’s fractional position be exposed as a user-agent-defined custom property, a registered property, or a dedicated property on::slider-thumb? See Custom Track Shapes.- Non-linear value scales (#1464): Logarithmic and other mappings remain an open issue and are not part of v1.
Open UI