Lynx/Modules/Zero/Slider
@sigx/lynx-zero · Beta · Component library

Slider#

A touch-driven value picker on zero's slider anatomy. The model is one number, or an array with one thumb per value. The rail runs horizontally or bottom to top, and the value is painted as inline percentages of the track that the skin never has to compute.

The parts are the same as the web component's. See Slider in @sigx/zero for the shared contract. This page covers what is different on Lynx.

Import#

TSX
import { Slider } from '@sigx/lynx-zero';

Slider is a compound whose only member is Slider.Root. The root renders every part itself. <Slider> and <Slider.Root> are the same component.

Usage#

TSX
import { component, signal } from '@sigx/lynx';
import { Slider } from '@sigx/lynx-zero';

export const Volume = component(() => {
    const state = signal({ volume: 40 });
    return () => (
        <Slider.Root
            label="Volume"
            model={() => state.volume}
            min={0}
            max={100}
            step={5}
            marks={[0, 50, 100]}
            showValue
            color="primary"
        />
    );
});

A touch anywhere on the control moves the value to that point, and dragging follows the finger. Values snap to the step grid, anchored at min, and clamp to [min, max]. label renders the visible label part and names the slider for a screen reader. showValue renders the value-text part, and formatValue formats each value in it.

A range#

Pass an array and the slider renders one thumb per value, with the range part spanning the lowest to the highest:

TSX
<Slider.Root
    defaultValue={[20, 80]}
    minStepsBetweenThumbs={5}
    onValueCommit={(v: number[]) => save(v)}
/>

A touch moves the nearest thumb and drags it. Thumbs never cross: each one stops at its neighbour. minStepsBetweenThumbs keeps them that many steps apart (default 0). A range written in unsorted order is read low to high, and every write emits the sorted array.

The events keep the model's shape: a scalar slider emits a number, a range slider a number[]. Slider.Root's handlers are typed from defaultValue or from the handler's own annotation, so onValueChange={(v: number) => …} and onValueCommit={(v: number[]) => …} both type-check. Without either, the handlers default to number.

Commit versus change#

valueChange fires on every step of a drag. valueCommit fires once when the drag ends, and only if the value actually moved. Use it for the expensive write:

TSX
<Slider.Root
    model={() => state.brightness}
    onValueCommit={(v: number) => persistBrightness(v)}
/>

Vertical#

orientation="vertical" runs the rail bottom to top. The orientation is stamped on the root and on every positioned part (zx-o-vertical), so a skin restyles the channel against it:

TSX
<Slider.Root orientation="vertical" defaultValue={60} showValue />

Read-only and disabled#

readonly refuses every touch: no drag, no press, no pressed flag. A read-only slider is still an accessibility element, with "read only" in its status, and it drops the adjustable trait so it does not invite a swipe. disabled refuses touches the same way. disabled, invalid and readonly are each the prop OR an enclosing Field.Root's.

How touches map to values#

The track's viewport rect (boundingClientRect) is measured against the touch's clientX / clientY. Android's layout-change rect is not page-relative, so this is what makes a touch land on the right value there, anywhere on the page. Every touch-down measures the track again, so a scrolled page is caught on the next touch.

The value paints as inline physical percentages of the track: left / width on a horizontal rail, top / height on a vertical one. That is the Lynx counterpart of the web's runtime --slider-percent property. The skin owns everything else about the track, range, thumbs and marks.

Props#

PropTypeDefaultDescription
modelnumber | number[]—Two-way binding. An array renders one thumb per value.
defaultValuenumber | number[]minInitial value when uncontrolled.
minnumber0Start of the rail.
maxnumber100End of the rail. A value at or below min falls back to min + 100.
stepnumber1The grid, anchored at min.
minStepsBetweenThumbsnumber0Range model: the closest two thumbs may come, in steps.
orientation'horizontal' | 'vertical''horizontal'A vertical rail runs bottom to top.
marksnumber[]—Positions on the rail, in value space, rendered as mark parts.
labelstring—Renders the label part and names the slider for the reader.
showValuebooleanfalseRenders the value-text part. A range shows its values joined by –.
formatValue(value: number) => stringStringFormats each value in value-text.
disabledbooleanfalseRefuses touches. ORed with the Field's.
invalidbooleanfalseStamps invalid on the root and the control. ORed with the Field's.
readonlybooleanfalseAnnounced, never moved by a touch. ORed with the Field's.
color / sizestringskin defaultThe design system's axes.
classstring—Extra classes, appended after the computed ones.

Events#

EventPayloadDescription
valueChange (onValueChange)number | number[]Every change, in the model's shape.
valueCommit (onValueCommit)number | number[]Once at the end of a drag that moved the value, in the model's shape.

Anatomy on Lynx#

PartElementFlagsNotes
rootviewdisabled, invalid, readonlyCarries the orientation and the axes.
labeltextdisabledRendered when label is set.
controlview (adjustable trait unless read-only)disabled, invalid, readonly, pressedThe touch target. pressed while a drag is in progress.
trackviewdisabled, readonlyThe measured rail.
rangeviewdisabledPositioned inline from the value.
markviewdisabledOne per entry in marks.
thumbviewdisabled, readonly, pressedOne per value. pressed on the thumb being dragged.
value-texttext—Rendered under showValue.

Marks are ticks only: there is no mark label text on Lynx. Not taken: the hidden-input part, name and form, and keyboard stepping.

See also#