registry:ui · Data Entry
range — the native slider, semantic surface
The native slider, semantic surface: a REAL input[type=range] as the base (2026-09-01 native rebase — jx-pure is the foundation; the registry version adds slots and semantics, never re-draws the control). The platform owns pointer, keyboard (arrows/Home/End/PageUp/Down), RTL, the label[for] binding and form submission (name= rides the input own FormData lane); the component adds the label row, the step-precision value readout, the optional tick ruler (one mark per step, inset to the thumb's travel; click a mark to snap, the wheel fine-tunes one input-step per detent behind a declarative touch-action axis grammar (wheel: true | 'x' | 'y' | 'xy' | 'none' | { x?, y? } with per-detent multipliers — owned gestures are swallowed and default-prevented, the page never scrolls under the slider), the ruler composes declaratively through RangeTick scales (marks at scale × step periods, lengths grading ascending by value), the vertical orientation face (orient attribute + writing-mode: min at the physical bottom, geometry on the width axis, the ruler maps bottom-up), the family error law (aria-invalid + aria-describedby + dashed-ring thumb), density tiers and external-write snapping. Paint is the jx-pure range recipe verbatim on the component hook — one visual law, two mounting surfaces.
range
A real input[type=range] underneath — drag, tap-to-jump, ←→/↑↓/Home/End/PageUp/Down and RTL are the engine's own, engine-tested; the component carries the paint, the label row, the readout and the error law. With ticks on, the ruler rides the thumb's travel box: click a mark to snap to it, wheel the slider to fine-tune one step per notch.
<Range
label="volume"
bind:value
min={0}
max={100}
step={1}
/>range
Native base — the platform contract, for free
A real input[type=range] is the control: pointer behavior, the full keyboard contract (←→/↑↓ step, Home/End jump, PageUp/PageDown stride), RTL mirroring, the label[for] binding and form submission are the ENGINE's own — nothing hand-held, nothing simulated. The registry surface adds what the bare element cannot: the label row with live readout, the tick ruler (one 4px mark per snap point as a repeating gradient, inset to the thumb's travel box — half a thumb per side — with the explicit end tick at 100%; click a mark to snap to it, wheel the slider for one-step fine-tuning), the family error law, and the jx-pure range paint (groove track, ringed disc thumb, primary fill) GENERATED onto the component's own hook from the same css-law — one visual law, machine-projected mounting surfaces.
4060.35Tab into a slider and drive it: ←→/↑↓ step by step, Home/End jump to the bounds, PageUp/PageDown
stride — all the platform's own range behavior, plus its native form semantics
(name= submits the numeric string through the
input's own FormData lane). The mirrored layout below costs the component nothing:
the engine draws RTL itself.
RTL + error wiring
volume is required
The error prop is the family law on the native
control too: aria-invalid + aria-describedby ride the input, the readout
takes the destructive mark, and the thumb border dashes — the monochrome
invalid signal, no second hue.
<!-- the base is the REAL input[type=range]; the component adds
the label row, readout, ticks and the error law -->
<Range label="volume" bind:value={volume} min={0} max={100} />
<!-- the ruler composes declaratively: RangeTick scales render marks at
scale × step periods, lengths grade ascending (ruler metaphor) —
click a mark to snap, the wheel fine-tunes one detent at a time -->
<Range label="gain" bind:value={gain} min={0} max={10} step={0.5}>
{#snippet ticks()}
<RangeTick scale={1} />
<RangeTick scale={5} />
<RangeTick scale={10} />
{/snippet}
</Range>
<!-- the wheel surface is declarative too (touch-action axis grammar):
'y' = plain wheel only, 'x' = shift+wheel, wheel={false} disables,
{'{ y: 0.2 }'} = five detents per input-step -->
<Range label="fine" bind:value={fine} wheel={'{ y: 0.2 }'} />
<!-- orientation="vertical" rides the platform's vertical face —
min at the physical bottom (writing-mode + orient), the ruler
maps bottom-up on the block axis -->
<Range label="fader" bind:value={fader} orientation="vertical" ticks />
<!-- keyboard is the platform's: ←→/↑↓ step · Home/End jump ·
PageUp/PageDown stride · RTL mirrors (the platform owns the
thumb; the law carries one :dir(rtl) fill-mirror rule) -->
<div dir="rtl"><Range label="volume (rtl)" bind:value={v} /></div>types
Range variants
Use a plain slider for continuous input, add steps and ticks when values are discrete, or flip the axis — vertical rides the platform's own vertical face with min at the physical bottom.
range · variants
<script lang="ts">
import Range, { RangeTick } from '@ui/range';
</script>
<div class="grid gap-4 sm:grid-cols-3">
<div class="border border-border p-4"><Range label="continuous" value={40} /></div>
<div class="border border-border p-4"><Range label="stepped" value={4} min={0} max={10} step={1} /></div>
<div class="border border-border p-4"><Range label="with ticks" value={50} ticks /></div>
<div class="border border-border p-4"><Range label="ruler (1/5/10)" bind:value={rulerValue} min={0} max={100} step={1}>{#snippet ticks()}<RangeTick scale={1} /><RangeTick scale={5} /><RangeTick scale={10} />{/snippet}</Range><span class="text-muted-foreground text-[12px]">click a mark to snap · wheel = one detent</span></div>
<div class="border border-border flex items-center justify-center p-4"><Range label="vertical fader" orientation="vertical" value={30} ticks min={0} max={10} step={1} /></div>
</div>usage
Usage
Bind the numeric value; min, max, step, ticks, and RTL all remain explicit props.
<!-- the base is the REAL input[type=range]; the component adds
the label row, readout, ticks and the error law -->
<Range label="volume" bind:value={volume} min={0} max={100} />
<!-- the ruler composes declaratively: RangeTick scales render marks at
scale × step periods, lengths grade ascending (ruler metaphor) —
click a mark to snap, the wheel fine-tunes one detent at a time -->
<Range label="gain" bind:value={gain} min={0} max={10} step={0.5}>
{#snippet ticks()}
<RangeTick scale={1} />
<RangeTick scale={5} />
<RangeTick scale={10} />
{/snippet}
</Range>
<!-- the wheel surface is declarative too (touch-action axis grammar):
'y' = plain wheel only, 'x' = shift+wheel, wheel={false} disables,
{'{ y: 0.2 }'} = five detents per input-step -->
<Range label="fine" bind:value={fine} wheel={'{ y: 0.2 }'} />
<!-- orientation="vertical" rides the platform's vertical face —
min at the physical bottom (writing-mode + orient), the ruler
maps bottom-up on the block axis -->
<Range label="fader" bind:value={fader} orientation="vertical" ticks />
<!-- keyboard is the platform's: ←→/↑↓ step · Home/End jump ·
PageUp/PageDown stride · RTL mirrors (the platform owns the
thumb; the law carries one :dir(rtl) fill-mirror rule) -->
<div dir="rtl"><Range label="volume (rtl)" bind:value={v} /></div>a11y
Accessibility
The native input IS the slider: the platform's implicit semantics and keyboard contract, named by a real label[for].
Keyboard
| Key | Action |
|---|---|
| Arrow keys | Change by step |
| Home / End | Jump to min / max |
| Page Up / Down | Change by a larger step when supported |
| Wheel (hover) | Fine-tune by detent — every detent = one input-step × the axis multiplier; declarative in the touch-action grammar: wheel is true/‘xy’ (default) | ‘y’ | ‘x’ | false/‘none’ | { x, y }. Owned gestures are swallowed and never scroll the page; ctrlKey pinch-zoom is never captured; disabled sliders ignore it. |
| Click a tick | Snap to that mark (pointer-only convenience; the ruler stays aria-hidden — the step semantics live on the input, whose arrows refine from the snapped value). |
| Form reset | The platform restores the input; the component re-syncs the bound value, the readout and aria-valuetext (a reset fires no input events by itself). |
ARIA
| Attribute | Value | Description |
|---|---|---|
| input[type=range] | implicit slider | The platform semantics: value/min/max/step are native truth; no roles to maintain. |
| label[for] | the field id | A REAL label binds to the labelable input (a div never could). |
| aria-valuetext | step-precision readout | The formatted value for assistive tech (decimal steps); follows form resets too. |
| .jx-slider-ticks | aria-hidden | The tick ruler rides the thumb’s travel box (half a thumb inset per side); a visual aid that also snaps on click — the step semantics stay on the input itself. |
theming
Density and tokens
The slider's geometry chain derives from its own size container (container-type: size): the thumb is the input's full height, the track and the ring are fractions of it — every internal proportion scales as one unit when the height lane changes.
| Token | Default | Source |
|---|---|---|
| --jx-range-thumb | 100cqh | component |
| --jx-range-track | calc(100cqh / 2.5) | component |
| --jx-range-ring | calc(100cqh / 8) | component |
| --jx-slider-fill-color | var(--primary) | component |
| --jx-tick-step | runtime step percentage (step / (max − min) × 100) | component |
| --jx-hit | 28 / 32 / 40 / 48px | density |
| --jx-icon | 16 / 18 / 20 / 24px | density |
| --jx-text | 11 / 12 / 13 / 15px | density |
| --jx-line | 16 / 18 / 20 / 24px | density |
axes
Universal props
The eight-axis surface (explicit-props): size · shape · radius · density · color · theme · elevation · motion — each axis takes named steps, auto (inherit the ambient context; stamps nothing), an exact number (px · coefficient · dp · hue per axis), or query() for responsive/container-conditional values. The family CONSUMES size and color: the native element never receives them (the §1 native collision rule).
range · universal props
<Range label="px number" min={0} max={100} value={40} size={14} density="small" />
<Range label="named steps" min={0} max={100} value={60} size="large" radius="medium" />api
API
Props define the numeric model, naming, visual options, and the error law; form lifecycle is native.
Properties
| Property | Type | Default | Description |
|---|---|---|---|
value bind | number | 0 | Bindable committed value; external writes snap into [min, max] on the step. |
| min | number | 0 | Lower bound (native). |
| max | number | 100 | Upper bound (native). |
| step | number | 1 | Increment for pointer and keyboard (native); step <= 0 or non-finite falls back to the platform default (1). |
| name | string | — | Form field name — the input submits its numeric string itself. |
| ticks | boolean | snippet | false | The tick ruler: true draws one default RangeTick per step (inset to the thumb’s travel); a ticks snippet composes RangeTick scales — marks at scale × step periods, lengths grading ascending by value. Clicking a mark snaps the value. |
| wheel | boolean | ‘x’ | ‘y’ | ‘xy’ | ‘none’ | { x?, y? } | true | The wheel fine-tune surface, declarative in the touch-action axis grammar: each axis is false (off), true (one input-step per detent) or a multiplier (0.2 → five detents per input-step). Owned gestures are swallowed and default-prevented; ctrlKey pinch-zoom is never captured. |
| orientation | 'horizontal' | 'vertical' | 'horizontal' | Vertical rides the platform’s vertical face (writing-mode + orient): min at the physical bottom, the ruler maps bottom-up on the block axis, height defaults to --jx-range-length (10rem). |
| showValue | boolean | true | Shows the current value readout. |
| error | string | — | Adds invalid state and message. |
| disabled | boolean | false | The platform disabled semantics (pointer, keyboard, form). |
Universal props
| Property | Type | Default | Description |
|---|---|---|---|
| size | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | the base scale — root font-size; parts size in em |
| shape | 'round' | 'scoop' | 'bevel' | 'notch' | 'square' | 'squircle' | 'auto' | 'auto' · ambient scope | corner geometry (CSS corner-shape; §14 degrade table) |
| radius | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | corner size; auto = the concentric broadcast (§3) |
| density | 'small' | 'medium' | 'large' | 'auto' | number | 'auto' · ambient scope | spacing/leading scale over the kernel channels (§4) |
| color | 'primary' | 'secondary' | 'error' | 'warn' | 'success' | 'info' | 'auto' | number | string | 'auto' · ambient scope | the hue axis of the fixed oklch primary system (§5) |
| theme | 'light' | 'dark' | 'system' | 'auto' | 'auto' · ambient scope | light/dark profile; system = the JS-mutable global (§6) |
| elevation | 'level-1' | 'level0' | 'level1' | 'level2' | 'level3' | 'level4' | 'level5' | 'auto' | number | 'auto' · ambient scope | official M3 levels over the surface ladder (§7) |
| motion | 'reduced' | 'subtle' | 'normal' | 'expressive' | 'auto' | number | 'auto' · ambient scope | intensity across the motion kernels (§8) |