BetaForms

Slider

A value chosen along a line.

Overview

A value chosen along a line.

First example

<Slider defaultValue={40} onValueCommit={save}>
<Slider.Output />
<Slider.Track>
<Slider.Fill />
<Slider.Thumb />
</Slider.Track>
</Slider>

Anatomy

<Slider>
  <Slider.Output />
  <Slider.Track>
    <Slider.Fill />
    <Slider.Thumb />
  </Slider.Track>
</Slider>
  • Slider — the value, the range, and the styles the slots read.
  • Slider.Output — the value in words. Optional.
  • Slider.Track — the line, and the only node that knows how long it is.
  • Slider.Fill — the part behind the thumb.
  • Slider.Thumb — what the finger holds.

Usage

Basic

<Slider defaultValue={40} onValueCommit={save}>
  <Slider.Output />
  <Slider.Track>
    <Slider.Fill />
    <Slider.Thumb />
  </Slider.Track>
</Slider>

Two callbacks, and the difference matters

onValueChange fires on every step the thumb crosses, including mid-drag. It is what a live preview reads.

onValueCommit fires once, when the finger lifts. It is where a network call belongs, because the first one can fire fifty times in a second.

Steps

<Slider min={5} max={95} step={10} />

Eleven stops at 5, 15, 25 … The snap counts steps from the minimum rather than rounding the value, so that range stops where it says it does and not at 10, 20, 30. step={0} is continuous.

Formatting the output

<Slider.Output>{value => `${value} %`}</Slider.Output>

children may be a function of the value. A format prop would have been the same thing with less room in it.

Style as props

<Slider.Track height={8} />
<Slider.Thumb width={32} />

Full RN names, full RN values (R14). Every node takes them.

Root props

SliderProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNo
sizeSliderSize | undefinedNo
orientationSliderOrientation | undefinedNo
radiusRadiusKey | undefinedNo
colorstring | undefinedNoThe tint (R7) — a raw value, never a token. Paints the fill and the thumb.
valueSliderValue | undefinedNoA number for one thumb, a pair for a range. The shape decides how many thumbs there are, and it comes back out the same way — a slider given `[20, 60]` reports `[20, 60]`, never `20`.
defaultValueSliderValue | undefinedNo
onValueChange((value: SliderValue) => void) | undefinedNoFires on every step a thumb crosses, including while it is being dragged.
onValueCommit((value: SliderValue) => void) | undefinedNoFires once, when the finger lifts. What a network call belongs on.
minnumber | undefinedNo
maxnumber | undefinedNo
stepnumber | undefinedNoSet it to `0` for a continuous slider.
isDisabledboolean | undefinedNo

Slots

SliderFillProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

This slot adds nothing to the props of its React Native node.

SliderOutputProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNode | ((value: SliderValue) => ReactNode)NoGiven the current value, so the caller can format it. Defaults to the number, or to the two ends joined by an en dash when the slider is a range.

SliderThumbProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
indexnumber | undefinedNoWhich thumb this is. `0` on a plain slider, `0` and `1` on a range — written out rather than conjured by the track, so a range is two slots you can see and style apart rather than one that silently became two.
accessibilityValueText((value: number) => string) | undefinedNoWhat a screen reader reads instead of the raw number.

SliderTrackProps

The node's inherited React Native props apply too, and so do its style props padding, margin, width and the rest.

PropTypeRequiredDescription
childrenReactNodeNo

Variants, sizes and colour

This component adds no visual axis of its own. The values it does take are in the generated types above and in the live demo.

Accessibility

The thumb is adjustable, carrying min, max and now, and it answers the platform's increment and decrement actions by one step — without that the platform guesses one percent of the range, which on a slider of five stops moves nothing.

Migration from legacy

Legacyv1
value / onValueChangeunchanged
onSlidingCompleteonValueCommit
minimumValuemin
maximumValuemax
themeColor="primary"color={theme.colors.accent}
customAppearance={{ … }}style on the slot that key named

Implementation notes

How it is put together

The travel is inset by half a thumb at each end. Otherwise the thumb hangs over the track's ends at the minimum and the maximum, and the fill runs out from under it. The fill runs to the thumb's centre rather than to the raw proportion, for the same reason.

A press anywhere on the track moves the thumb there. It is the half of a slider people forget: dragging a narrow thumb is a fine gesture on a mouse and a poor one on a finger.

The thumb grows 15% under the press rather than moving. The finger is already covering it, so the scale is what you see in the gap around it — and it is the only confirmation a slider can give that the drag has started. Stiff and well damped: a confirmation should arrive, not wobble.

The value crosses to the JS thread and the scale does not. The pan computes the new position on the UI thread and hands the value back over runOnJS, which is the one hop that has to happen — the value is React state. The scale stays where it is.

Not here yet

A range slider, with two thumbs. The context already carries what a second thumb would need, and useSlider is exported for anyone who wants to write one — but two thumbs is two values, a different type for value, and rules about which one gives way. Worth its own change.

A vertical slider. The same arithmetic on the other axis, but also a different gesture and a different layout.