Examples from Open UI research
<rangegroup>
element.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 */ }
::-moz-range-thumb { /* Styles the thumb */ }
::-moz-range-track { /* Styles the track */ }
::-moz-range-progress { /* Styles the progress/fill below the thumb */ }
::-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.
<rangegroup>
Section titled Multi-Handle%20Support%20with%20%3Crangegroup%3EBased 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:
<rangegroup>
will still display individual range inputs<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.
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).
A <rangegroup>
can have an associated label by referencing the id
and use the for
attribute on the label.
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.
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.
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.
<rangegroup>
AttributesSection titled %3Crangegroup%3E%20Attributesmin
: 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>
)Section titled %3Cinput%20type%3D%22range%22%3E%20Attributes%20%28within%20a%20%3Crangegroup%3E%29In 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 thumbname
: 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.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%;
}
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]
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;
}
<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.::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?<datalist>
with <rangegroup>
elements?<datalist>
? These could be auto-generated based on a step attribute of a rangegroupmin
and max
attributes interact with the parent <rangegroup>
’s min
and max
attributes, should they overrule?stepbetween
or other range limits?<rangegroup>
elements? Should they submit as a single value (comma-separated) or as multiple values with the same name?