Components · Slider
Slider
A continuous numeric control for thresholds, lookbacks and weights: a small thumb, a much larger hit target, and a value shown only while it is being used.
Examples
One value
Arrow keys step by step, Home and End jump to the bounds, and the value bubble appears on hover, focus and drag.
Signals below this score are dropped.
Relative weight in the composite score.
<Slider value={confidence} onValueChange={setConfidence} min={0} max={1} step={0.01} />
<Slider defaultValue={45} showValue="always" formatValue={(value) => `${value}%`} />A range
RangeSlider is two thumbs over one inclusive pair. The value is always handed back sorted, so a consumer never re-orders it.
Never closer than five points.
18 to 64
<RangeSlider value={band} onValueChange={setBand} min={0} max={100} minStepsBetweenThumbs={5} />Compact, with nothing to reveal
`showValue="never"` and ticks without captions reserve no room, so the root is the groove and the thumb — 20px at `md` rather than 38px. That is the shape a toolbar or a table cell wants, and it is the only configuration in which the slider does not grow.
{/* `auto` (the default) reserves the bubble's room: the value appears during
the drag, and a row that grew mid-gesture would push the content under it.
`never` opts out of the decoration entirely. */}
<Slider defaultValue={40} showValue="never" label="Opacity" />
<RangeSlider defaultValue={[25, 75]} showValue="never" label="Band" />
<Slider defaultValue={50} showValue="never" marks={[{ value: 0 }, { value: 100 }]} label="Ticks" />States and orientation
Disabled is expressed with colour, never with whole-component opacity, so the label and value stay readable. A vertical slider is sized by the consumer's box.
Invalid swaps the accent for the negative tone and sets aria-invalid on the thumb.
35
Range
Marks are declared by the consumer, never generated.
<Slider defaultValue={40} label="Disabled" disabled />
<Slider defaultValue={60} label="Invalid" invalid />
<Slider orientation="vertical" defaultValue={35} label="Vertical" className="h-40" />In a form
A slider is a control like any other: it takes its label, description and error from Field, and it submits through the form's own lifecycle.
<Form errors={errors} onSubmit={save}>
<FormErrorSummary />
<Field label="Drawdown limit" description="Percent of peak equity before the run stops.">
<Slider defaultValue={12} min={0} max={50} step={1} />
</Field>
<Button type="submit">Save limits</Button>
</Form>Settings pattern
The shape a real settings pane takes: a label, a live value, a switch for the on/off axis and a slider for the amount.
Alert on drawdown
Posts to the research channel when a run crosses the limit.
Applied to the daily equity curve.
<Toggle pressed={alerting} onPressedChange={setAlerting} aria-label="Alert on drawdown">
Alert on drawdown
</Toggle>
<Field label="Limit" description="Percent of peak equity.">
<Slider value={limit} onValueChange={setLimit} min={0} max={50} showValue="always" />
</Field>Usage
import { RangeSlider, Slider } from "@luc/ui/slider";<RangeSlider value={band} onValueChange={setBand} min={0} max={100} aria-label="Percentile band" />`value` is a number on `Slider` and a two-item pair on `RangeSlider`, which hands the pair back sorted so a caller never re-orders it.
API
Read from the published TypeScript API, so a prop that is not listed here does not exist and a default that is listed is the one the compiler sees.
| Export | Kind | Signature |
|---|---|---|
RangeSlider | 22 documented props | ({ value, defaultValue, onValueChange, onValueCommit, min, max, thumbLabels, ...props }: RangeSliderProps) => import("@types/react").JSX.Element |
Slider | 21 documented props | ({ value, defaultValue, onValueChange, onValueCommit, min, max, ...props }: SliderProps) => import("@types/react").JSX.Element |
Slider props
Type: SliderProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
defaultValue? | number | undefined | — | |
dir?inherited | Direction | undefined | — | |
disabled?inherited | boolean | undefined | — | |
form?inherited | string | undefined | — | |
formatValue? | ((value: number) => ReactNode) | undefined | — | |
invalid? | boolean | undefined | — | |
inverted?inherited | boolean | undefined | — | |
label? | string | undefined | — | Accessible name for the thumb when no `Field` label is present. |
marks? | readonly SliderMark[] | undefined | — | Optional ticks. Never generated automatically. |
max?inherited | number | undefined | — | |
min?inherited | number | undefined | — | |
minStepsBetweenThumbs?inherited | number | undefined | — | |
name?inherited | string | undefined | — | |
onValueChange? | ((value: number) => void) | undefined | — | |
onValueCommit? | ((value: number) => void) | undefined | — | Fired when a drag or key-step settles — the right place to commit. |
orientation?inherited | "horizontal" | "vertical" | undefined | — | |
showValue? | ValueDisplay | undefined | — | When to print the value next to the thumb. Default `auto` (hover, focus, drag). |
size? | ControlSize | undefined | — | |
step?inherited | number | undefined | — | |
value? | number | undefined | — | Single value. |
RangeSlider props22 props
RangeSlider props
Type: RangeSliderProps
Values the compiler reads from the declaration.
| Prop | Type | Default | Notes |
|---|---|---|---|
asChild?inherited | boolean | undefined | — | |
defaultValue? | readonly [number, number] | undefined | — | |
dir?inherited | Direction | undefined | — | |
disabled?inherited | boolean | undefined | — | |
form?inherited | string | undefined | — | |
formatValue? | ((value: number) => ReactNode) | undefined | — | |
invalid? | boolean | undefined | — | |
inverted?inherited | boolean | undefined | — | |
label? | string | undefined | — | |
marks? | readonly SliderMark[] | undefined | — | Optional ticks. Never generated automatically. |
max?inherited | number | undefined | — | |
min?inherited | number | undefined | — | |
minStepsBetweenThumbs? | number | undefined | — | Minimum gap between the two thumbs, in `step` increments. |
name?inherited | string | undefined | — | |
onValueChange? | ((value: [number, number]) => void) | undefined | — | |
onValueCommit? | ((value: [number, number]) => void) | undefined | — | |
orientation?inherited | "horizontal" | "vertical" | undefined | — | |
showValue? | ValueDisplay | undefined | — | When to print the value next to the thumb. Default `auto` (hover, focus, drag). |
size? | ControlSize | undefined | — | |
step?inherited | number | undefined | — | |
thumbLabels? | readonly [string, string] | undefined | — | One accessible name per thumb, when "minimum"/"maximum" is not enough. |
value? | readonly [number, number] | undefined | — | Inclusive `[minimum, maximum]` pair. |
Guidelines
Do
- Use a slider when the range matters more than the exact number; a value that has to be typed exactly is `NumberInput`.
- Show the formatted value and keep the label outside the control: a bare thumb is a guess, and a bubble is not a name.
- Name each end of a range with `thumbLabels`, so the pair is not announced as two identical controls.
Don't
- Re-purpose the value bubble as the permanent label — it appears on hover, focus and drag by design, and is absent at rest.
- Let the two ends cross: `minStepsBetweenThumbs` is the gap the thumbs keep, and a band that can invert is not a band.
- Rescale a percentage and format it as a fraction: `formatValue` is display only and the submitted number is unchanged.
Two components, one shell
- Slider
- One value: a threshold, a weight, a lookback. `value` is a number.
- RangeSlider
- Two ends of one band. The pair comes back sorted, and `minStepsBetweenThumbs` is the gap the thumbs keep.
Accessibility
Every thumb is a focusable slider with its own value bounds, so the arrow keys step it, Home and End jump to the ends, and the value is announced from the thumb rather than from the decorative bubble. A range therefore needs one name per thumb — `thumbLabels` for a pair, `label` for a single value — and a disabled slider keeps its colour treatment so the label and the value stay readable.
Related