Slider

A value picked from a range by dragging a handle along a track.

import { Slider } from "@delacour/native-ui/slider";
<Slider value={volume} onChange={setVolume}>
  <Slider.Output />
  <Slider.Track>
    {() => (
      <>
        <Slider.Fill />
        <Slider.Thumb />
      </>
    )}
  </Slider.Track>
</Slider>

Colours: default, primary, success, warning, danger, info. Sizes: sm, md, lg — the groove's thickness, the thumb's diameter and the readout's type step. Orientations: horizontal, vertical.

The package's first drag-driven control, and its first Gesture.Pan().

Anatomy

Anatomy
Anatomy
import { Slider } from "@delacour/native-ui/slider";import type { ReactElement } from "react";export function Demo(): ReactElement {	return (		<Slider defaultValue={30}>			<Slider.Output />			<Slider.Track>				<Slider.Fill />				<Slider.Thumb testID="slider-thumb" />			</Slider.Track>		</Slider>	);}
Sizes
Sizes
import { SLIDER_SIZES, Slider } from "@delacour/native-ui/slider";import { Text } from "@delacour/native-ui/text";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement {	return (		<View className="gap-5">			{SLIDER_SIZES.map((size) => (				<View className="gap-2" key={size}>					<Text.Caption color="muted">{size}</Text.Caption>					<Slider defaultValue={50} size={size}>						<Slider.Output />						<Slider.Track>							<Slider.Fill />							<Slider.Thumb testID={`thumb-${size}`} />						</Slider.Track>					</Slider>				</View>			))}		</View>	);}
PartWhat it is
SliderOwns the value, the gesture and the axes
Slider.OutputThe current value, formatted. Give it a function to label it yourself
Slider.TrackThe groove the thumbs run along, and the surface the drag is claimed on
Slider.FillThe painted part of the groove, from the minimum — or between two thumbs
Slider.ThumbThe grab handle. index picks which value it drives in a range

The anatomy is written out, never assembled from props. A range's thumb count is data, so Slider.Track takes a function and is handed the settled state to map over. Radio composes its own indicator in because a radio has exactly one; a slider does not know how many it has until it is told.

Ranges

A range
A range
import { Slider } from "@delacour/native-ui/slider";import { Text } from "@delacour/native-ui/text";import { type ReactElement, useState } from "react";import { View } from "react-native";const CURRENCY = { currency: "NZD", style: "currency", maximumFractionDigits: 0 } as const;export function Demo(): ReactElement {	const [price, setPrice] = useState<number[]>([200, 800]);	return (		<Slider			color="success"			formatOptions={CURRENCY}			maxValue={1000}			onChange={(next) => setPrice(next as number[])}			step={10}			value={price}		>			<View className="flex-row items-center justify-between">				<Text.Label>Price range</Text.Label>				<Slider.Output />			</View>			<Slider.Track>				{({ values }) => (					<>						<Slider.Fill />						{values.map((_, index) => (							<Slider.Thumb index={index} key={index} testID={`thumb-${index}`} />						))}					</>				)}			</Slider.Track>		</Slider>	);}

Pass an array and the slider becomes a range, one thumb per entry.

<Slider value={[20, 80]} onChange={setRange}>
  <Slider.Track>
    {({ values }) => (
      <>
        <Slider.Fill />
        {values.map((_, index) => (
          <Slider.Thumb index={index} key={index} />
        ))}
      </>
    )}
  </Slider.Track>
</Slider>

Which shape you speak is locked on first render. A slider that silently started reporting an array to a caller holding a number would be a bug with no error attached, so a flip warns once in development and the slider keeps following you.

The whole track is the target

Colours
Colours
import { SLIDER_COLORS, Slider } from "@delacour/native-ui/slider";import { Text } from "@delacour/native-ui/text";import type { ReactElement } from "react";import { View } from "react-native";export function Demo(): ReactElement {	return (		<View className="gap-5">			{SLIDER_COLORS.map((color) => (				<View className="gap-2" key={color}>					<Text.Caption color="muted">{color}</Text.Caption>					<Slider color={color} defaultValue={65}>						<Slider.Track>							<Slider.Fill />							<Slider.Thumb testID={`thumb-${color}`} />						</Slider.Track>					</Slider>				</View>			))}		</View>	);}
Vertical
Vertical
import { Slider } from "@delacour/native-ui/slider";import type { ReactElement } from "react";import { View } from "react-native";const VERTICAL_COLORS = ["default", "success", "warning"] as const;export function Demo(): ReactElement {	return (		<View className="h-56 flex-row justify-around">			{VERTICAL_COLORS.map((color) => (				<Slider color={color} defaultValue={45} key={color} orientation="vertical">					<Slider.Track>						<Slider.Fill />						<Slider.Thumb testID={`thumb-${color}`} />					</Slider.Track>				</Slider>			))}		</View>	);}

Touching down grabs the nearest thumb and moves it to the finger; there is no separate tap mode. Slider.Thumb holds no gesture of its own, which is what makes a press on empty track lift the handle it is about to move.

That is also why the accessibility role and actions live on the thumb — with no gesture there, they are the only path assistive technology has to the value.

Two change callbacks

onChange fires on every change during the drag. onChangeEnd fires once, when the drag is released — that is where a network write belongs.

Steps and haptics

step is the increment the value snaps to; 0 is continuous. Haptics are rate-limited by distance rather than a clock (SLIDER_HAPTIC_MIN_TRAVEL), a continuous slider never ticks, and both ends of the range always do.

Gotchas

A vertical slider needs a definite height

It counts up from the bottom and fills whatever it is given, so a wrapper with h-48 or flex-1 is not optional.

Inside a ScrollView on iOS

The slider feels late until the scrollable sets delaysContentTouches={false}. That is a prop at your call site, not something the component can set.

API

Prop

Type

On this page