Playground
A slider with two thumbs, for a start and an end: a price band, a time window, an altitude range. It is the sibling of sb-slider, and is built on two native range inputs, so arrow keys, Page Up/Down, Home/End and touch work on each thumb. The thumbs can meet but not cross.
The range is one value: value='{"start":20,"end":60}' in, { start, end } out. Both ends change together in one sb-change and one command. See Components with several values.
Examples
Basic
<div style="display: grid; gap: 20px; inline-size: min(100%, 22rem)">
<sb-range label="Orbit" value='{"start":300,"end":800}' min="160" max="2000" step="10" unit=" km" ticks></sb-range>
<sb-range label="Launch window" value='{"start":9,"end":17}' min="0" max="24" unit=":00"></sb-range>
<sb-range label="Signal" value='{"start":0.2,"end":0.8}' min="0" max="1" step="0.01"></sb-range>
</div>
A missing part is the bound: value='{"end":40}' starts at min, and without a value the whole range is selected.
The whole range in one event
sb-change fires once per committed drag, with both ends:
<div data-signals="{_window: ''}" style="display: grid; gap: 12px; inline-size: min(100%, 22rem)">
<sb-range name="window" label="Launch window" value='{"start":9,"end":17}' min="0" max="24" unit=":00"
data-on:sb-change="$_window = JSON.stringify(evt.detail)"></sb-range>
<code data-text="$_window || 'Drag a thumb…'"></code>
</div>
The live value is the value property: el.value returns { start, end }, and setting it accepts { start, end } or [start, end].
With commands
Post the detail as it is. The server validates the range as a whole and renders the new value attribute:
<sb-range name="window" confirm value='{"start":9,"end":17}' min="0" max="24"
data-on:sb-change="@post('/cmd/window', {payload: {tabid: $tabid, ...evt.detail}})"
data-on:datastar-fetch="evt.detail.el === el && evt.detail.type === 'error' && el.revert()"></sb-range>
With confirm, the host has :state(pending) while either end differs from the server's attribute, and revert() restores both ends. A command can never leave the server with only half a change. See Commands and components.
sb-range:state(pending) { opacity: 0.7; }
Styling
The thumbs and the fill follow --sb-text-1, --sb-brand-light and --sb-brand; --sb-notch: 0 gives rounded corners. Parts: label, value, rail, and input (plus start / end) for each native input.
Accessibility
The field is a group named by its label. Each thumb is a native slider, announced as "
Installation
Add Datastar with Rocket and the Starbase autoloader once per page, then use the tag. The autoloader imports each component the first time its tag appears, including tags added later by a Datastar morph.
<!-- Once per page: Datastar with Rocket, and the Starbase autoloader.
It loads every <sb-…> component the first time its tag appears. -->
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/autoloader.js"></script>
<!-- Optional, no flash of undefined elements: class="sb-cloak" on <html>, and -->
<style>.sb-cloak :not(:defined) { visibility: hidden }</style>
<sb-range label="Orbit" value='{"start":300,"end":800}' min="160" max="2000" step="10" unit=" km" style="inline-size: 13rem"></sb-range>
<!-- In production, pin today's catalog instead of the latest: the browser then
refuses any file that changed. Add "integrity" to the import map above: the
hashes from https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/importmap.json and Datastar's, below. -->
<!--
<script type="importmap">
{ "imports": { "datastar": "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js" },
"integrity": { "https://cdn.jsdelivr.net/gh/starfederation/datastar@v1.0.4/bundles/datastar-rocket.js": "sha384-vUxZojLrF1Ar3de5h7VINqhJXBgjyZS4U49pHvGa87kum6j5Xn6JRziuARNw5ELG", "…": "…from importmap.json" } }
</script>
<script type="module" src="https://starbase.zweiundeins.gmbh/c/@0a1ad1bf98a4/autoloader.js" integrity="sha384-IvSCUjEWPS+wwXunU+48x96KmyWDx/Y4LeCwCL8cWFqJ/zHmTsTOsSMrzTOcpW3D"></script>
-->
<!-- Or load just this component, pinned to this version: -->
<!-- <script type="module" src="https://starbase.zweiundeins.gmbh/c/range@73041f207ebc/range.js" integrity="sha384-HewP4YrbHiRIdsdsTAatY04VNILm8Z+CgptW8QHBX9tl4H7qVsW3kiBGeuPfjL4H"></script> -->Size
Each file compressed on its own, the way it is served (gzip -9, brotli -11). Datastar and Rocket are shared by every component and not counted.
| File | Original | gzip | brotli |
|---|---|---|---|
range.js | 9.6 kB | 3.6 kB | 3.1 kB |
API reference
Props
| Attribute | Type | Default | Description |
|---|---|---|---|
value | object | {
"start": null,
"end": null
} | The range, as JSON: {"start": 20, "end": 60}; a missing part is min or max. A new value from the server replaces it; the live value is the value property. |
min | number | 0 | Minimum. |
max | number | 100 | Maximum. |
step | number | 1 | Step size (0 for continuous). |
label | string | "" | Visible label; the thumbs are announced as "<label> start" and "<label> end". |
unit | string | "" | Suffix after the shown values, e.g. "°" or " kg". |
show-value | boolean | true | Show the current range next to the label. |
ticks | boolean | false | Show min and max below the track. |
disabled | boolean | false | Disable interaction. |
confirm | boolean | false | Server-confirmed value: :state(pending) while the local range differs from the server's value attribute (see revert()). |
name | string | "" | Name reported in sb-change (e.g. the field of a command). |
Events
| Name | Description |
|---|---|
input | While dragging either thumb (native, re-targeted to the host). |
change | When the range is committed. |
sb-change | When the range is committed. detail: { name, value: { start, end } }: the whole range, one command. |