<bmx-slider>
A value chosen along a track, or a range chosen between two of them. Ticks, a value bubble, four combinations of orientation and writing direction, and a keyboard that does everything the pointer does.
24 properties · 2 events · 3 methods · 8 parts
Example
Show markup
<div class="row">
<bmx-slider label="Volume" value="60" ticks="10" show-value="auto" description="Arrow keys step, Page Up and Down take a tenth of the range, Home and End go to the ends."></bmx-slider>
</div>
<div class="row">
<bmx-slider id="ex-slider-price" label="Price range" range="true" min="0" max="5000" step="50" min-distance="250" show-value="always" description="Two role="slider" elements whose announced bounds are each other, so the thumbs cannot cross."></bmx-slider>
</div>
<div class="row">
<bmx-slider label="Rating" value="3" min="1" max="5" step="1" ticks="1" tick-labels="true" size="lg" tone="warning"></bmx-slider>
</div>
<script type="module">
await customElements.whenDefined('bmx-slider');
const price = document.getElementById('ex-slider-price');
// `values` and `format` are both properties: an array and a function, neither
// of which fits in an attribute.
price.values = [1000, 3500];
price.format = value => `£${value.toLocaleString('en-GB')}`;
</script>
WHY THIS ONE IS NOT A NATIVE <input type="range">
§8 says real elements rather than ARIA impersonations, and bmx-checkbox and
bmx-switch both follow it. This component does not, and the reason is the
range.
There is no native two-ended slider. The usual answer is two <input type="range"> elements overlaid, and it is a bad one: when both thumbs sit
on the same value the one underneath cannot be grabbed at all, so the control
has a state it cannot get out of, and the fix is juggling pointer-events
by which half of the track the pointer is in. Shipping the single-value case
on a native input and the range on something else would then mean two
keyboards, two sets of styling hooks and two sets of bugs in one tag.
So both are role="slider" elements, which is the pattern WAI-ARIA
documents for exactly this. What that costs is the keyboard and the drag,
which the platform would otherwise have given us - so the keyboard is
valueForKey in src/core/slider.ts with its own tests, and the drag uses
pointer capture, which is the part only a browser can prove.
A RANGE IS TWO SLIDERS
Not one slider with two values. Each thumb is its own role="slider" with
its own aria-valuenow, and their aria-valuemin and aria-valuemax bound
each other - so a screen reader user arrowing the lower thumb is told where
it may go, which is the information a single element with two values has
nowhere to put.
The thumbs do not cross. The reasoning is in boundThumb: a values[0] that
means the low end at one moment and the high end at the next hands a consumer
reading it mid-drag a value belonging to the other thumb.
aria-valuetext MATTERS AS MUCH AS aria-valuenow
A price slider announcing "1200" is telling the user a number, not a price,
and a rating slider announcing "3" is not saying "3 stars". format supplies
the text, and it is used for the bubble and the announcement together so the
two can never disagree.
Properties
| Property | Attribute | Type | Default | Description |
|---|---|---|---|---|
autoFocus |
auto-focus |
boolean |
false |
Focus the first thumb once it has rendered. |
description |
description |
string |
— | Help text below the track. |
disabled |
disabled |
boolean |
false |
Disable the slider. |
errorText |
error-text |
string |
— | An error supplied by the consumer. |
format |
property only | BmxSliderFormatter |
— | Turns a value into what the user reads. Used for the bubble, the tick labels and aria-valuetext together, so the three can never disagree - a price slider that shows "£1,200" and announces "1200" is announcing a number rather than a price. |
fullWidth |
full-width |
boolean |
false |
Stretch to the width of the container. |
hideLabel |
hide-label |
boolean |
false |
Hide the label visually while keeping it as the accessible name. |
label |
label |
string |
— | The field's label. Required unless the label slot is used. |
largeStep |
large-step |
number |
— | How far Page Up and Page Down travel. Defaults to a tenth of the range. |
max |
max |
number |
100 |
The highest value. |
min |
min |
number |
0 |
The lowest value. |
minDistance |
min-distance |
number |
0 |
The smallest gap the two thumbs may be apart. |
name |
name |
string |
— | The field's name in the form it belongs to. |
orientation |
orientation |
BmxOrientation |
'horizontal' |
Which way the track runs. A vertical track runs upwards. |
range |
range |
boolean |
false |
Two thumbs rather than one. |
readonly |
readonly |
boolean |
false |
Make the slider read-only. It still submits and is still focusable. |
showValue |
show-value |
BmxSliderValueDisplay |
'auto' |
When the value is shown above the thumb. |
size |
size |
BmxSize |
'md' |
Size step. Scales the track, the thumbs and the text together. |
step |
step |
number |
1 |
The interval between reachable values, counted from min. |
tickLabels |
tick-labels |
boolean |
false |
Label the ticks with their values. |
ticks |
ticks |
number |
0 |
Draw a tick every N steps. 0 draws none. A tick per step on a long scale is thousands of elements nobody can see, so anything over a hundred draws none either. |
tone |
tone |
BmxTone |
'primary' |
Semantic colour role for the fill, the thumbs and the focus ring. |
value |
value |
number |
0 |
The value, in single mode. Two-way: the component writes back to it. |
values |
property only | number[] |
[] |
The two ends, in range mode. Two-way. Accepts a comma-separated or JSON string as well, because an attribute is the only channel some templates have. See src/core/markup.ts. |
Events
| Event | Detail | Description |
|---|---|---|
bmxChange |
BmxSliderChangeDetail |
Fired when a movement finishes - on pointer release, or on each key press. |
bmxInput |
BmxSliderChangeDetail |
Fired continuously while a thumb moves. |
Methods
| Method | Signature | Description |
|---|---|---|
getValues |
getValues() => Promise<number[]> |
Every thumb's value, low to high. |
removeFocus |
removeFocus() => Promise<void> |
Remove focus from whichever thumb has it. |
setFocus |
setFocus(index?: number, options?: FocusOptions) => Promise<void> |
Focus a thumb. The first one, unless another is named. |
Slots
| Slot | Description |
|---|---|
(default) |
The default slot |
description |
Rich help text, in place of the description property. |
label |
Rich label content, in place of the label property. |
CSS shadow parts
| Part | Description |
|---|---|
bubble |
The value shown above the thumb. |
description |
The help text. |
fill |
The part of the track between the ends of the selection. |
label |
The label element. |
thumb |
A draggable thumb. |
tick |
One tick mark. |
tick-label |
One tick's text. |
track |
The full-length track. |
CSS custom properties
| Property | Description |
|---|---|
--bmx-slider-duration |
How long the thumb takes to settle after a keystroke. Zero while dragging, and under reduced motion. |
--bmx-slider-fill-color |
The selected part. Defaults to the tone's solid colour. |
--bmx-slider-font-size |
The tick labels and the value bubble. |
--bmx-slider-length |
A vertical slider's length. A horizontal one is sized by inline-size on the host. |
--bmx-slider-radius |
Corner radius of the track and the fill. |
--bmx-slider-thumb-border-color |
The thumb's ring. Defaults to the tone's solid colour. |
--bmx-slider-thumb-border-width |
The ring around the thumb. |
--bmx-slider-thumb-color |
The thumb's fill. |
--bmx-slider-thumb-shadow |
The thumb's shadow, which is what lifts it off the track. |
--bmx-slider-thumb-size |
The thumb's diameter. |
--bmx-slider-tick-color |
The tick marks. |
--bmx-slider-track-color |
The unfilled part of the track. |
--bmx-slider-track-size |
The track's thickness. |