Skip to content

Slider

Slider provides intuitive value selection through gesture-based interactions with smooth animations and extensive customization options. It includes both single-value and range slider variants with built-in accessibility features and themed styling.

import { Slider, RangeSlider } from 'rnc-theme';
import type { SliderRef, RangeSliderRef } from 'rnc-theme';
<Slider
min={0}
max={100}
initialValue={50}
onValueChange={(value) => console.log(value)}
/>
PropTypeDefaultDescription
minnumber0Minimum slider value
maxnumber100Maximum slider value
stepnumber1Value increment step
initialValuenumberminInitial slider value
widthnumber300Slider track width
heightnumberautoContainer height (based on size)
disabledbooleanfalseDisable slider interactions
variantComponentVariant'default'Visual style variant
sizeComponentSize'md'Slider size (xs, sm, md, lg, xl)
trackColorstringautoInactive track color
activeTrackColorstringautoActive track color
thumbColorstringautoThumb background color
thumbSizenumberautoThumb diameter (based on size)
showLabelbooleanfalseShow value label on drag
labelFormatter(value: number) => stringvalue.toString()Format label text
onValueChange(value: number) => void-Called when value changes
onSlidingStart(value: number) => void-Called when sliding starts
onSlidingComplete(value: number) => void-Called when sliding ends
PropTypeDefaultDescription
minnumber0Minimum slider value
maxnumber100Maximum slider value
stepnumber1Value increment step
initialMinValuenumberminInitial minimum value
initialMaxValuenumbermaxInitial maximum value
minDistancenumber0Minimum distance between thumbs
widthnumber300Slider track width
heightnumberautoContainer height (based on size)
disabledbooleanfalseDisable slider interactions
variantComponentVariant'default'Visual style variant
sizeComponentSize'md'Slider size (xs, sm, md, lg, xl)
trackColorstringautoInactive track color
activeTrackColorstringautoActive track color
thumbColorstringautoThumb background color
thumbSizenumberautoThumb diameter (based on size)
showLabelsbooleanfalseShow value labels on drag
labelFormatter(value: number) => stringvalue.toString()Format label text
onValueChange(values: {min: number, max: number}) => void-Called when values change
onSlidingStart(values: {min: number, max: number}) => void-Called when sliding starts
onSlidingComplete(values: {min: number, max: number}) => void-Called when sliding ends
VariantDescriptionUse Case
defaultStandard theme colorsGeneral purpose sliders
primaryPrimary brand colorsImportant value selections
secondarySecondary brand colorsSecondary controls
successSuccess/positive colorsPositive range selections
errorError/danger colorsWarning or limit controls
warningWarning colorsCaution range selections
infoInformation colorsInformational controls
outlineTransparent active trackMinimal design preference
filledFilled appearanceEnhanced visual prominence
ghostSubtle appearanceMinimal visual impact
SizeTrack HeightThumb SizeContainer Height
xs2px14px32px
sm3px16px36px
md4px20px40px
lg5px24px44px
xl6px28px48px
const VolumeControl = () => {
const [volume, setVolume] = useState(75);
return (
<VStack spacing="md" padding="lg">
<HStack spacing="md" align="center">
<Icon name="volume-low" size={20} />
<Slider
min={0}
max={100}
initialValue={volume}
width={200}
variant="primary"
size="md"
labelFormatter={(value) => `${value}%`}
showLabel={true}
onValueChange={setVolume}
onSlidingComplete={(value) => {
// Apply volume change
AudioService.setVolume(value / 100);
}}
/>
<Icon name="volume-high" size={20} />
</HStack>
<Text style={{ textAlign: 'center' }}>
Volume: {volume}%
</Text>
</VStack>
);
};
const PriceRangeFilter = () => {
const [priceRange, setPriceRange] = useState({ min: 50, max: 200 });
return (
<VStack spacing="lg" padding="lg">
<Text variant="heading">Price Range</Text>
<RangeSlider
min={0}
max={500}
step={5}
initialMinValue={priceRange.min}
initialMaxValue={priceRange.max}
minDistance={10}
width={280}
variant="success"
size="md"
showLabels={true}
labelFormatter={(value) => `$${value}`}
onValueChange={setPriceRange}
onSlidingComplete={(values) => {
// Apply filter
applyPriceFilter(values.min, values.max);
}}
/>
<HStack justify="space-between">
<Text>${priceRange.min}</Text>
<Text>${priceRange.max}</Text>
</HStack>
</VStack>
);
};
const TemperatureControl = () => {
const [temperature, setTemperature] = useState(22);
const getTemperatureColor = (temp: number) => {
if (temp < 18) return 'info';
if (temp < 24) return 'success';
if (temp < 28) return 'warning';
return 'error';
};
return (
<Card padding="xl">
<VStack spacing="lg" align="center">
<Text variant="title">Room Temperature</Text>
<Slider
min={10}
max={35}
step={0.5}
initialValue={temperature}
width={250}
variant={getTemperatureColor(temperature)}
size="lg"
showLabel={true}
labelFormatter={(value) => `${value}°C`}
onValueChange={setTemperature}
onSlidingComplete={(value) => {
// Send to thermostat
ThermostatAPI.setTemperature(value);
}}
/>
<VStack align="center">
<Text variant="display" size="xl">
{temperature}°C
</Text>
<Text variant="caption" color="muted">
Target Temperature
</Text>
</VStack>
</VStack>
</Card>
);
};
const ImageCropTool = () => {
const [cropValues, setCropValues] = useState({ min: 10, max: 90 });
const [brightness, setBrightness] = useState(100);
const [contrast, setContrast] = useState(100);
return (
<VStack spacing="xl" padding="lg">
<Text variant="heading">Image Adjustments</Text>
{/* Crop Range */}
<VStack spacing="sm">
<Text variant="label">Crop Range</Text>
<RangeSlider
min={0}
max={100}
initialMinValue={cropValues.min}
initialMaxValue={cropValues.max}
minDistance={5}
width={300}
variant="primary"
size="md"
showLabels={true}
labelFormatter={(value) => `${value}%`}
onValueChange={setCropValues}
/>
</VStack>
{/* Brightness */}
<VStack spacing="sm">
<HStack justify="space-between">
<Text variant="label">Brightness</Text>
<Text variant="caption">{brightness}%</Text>
</HStack>
<Slider
min={0}
max={200}
initialValue={brightness}
width={300}
variant="warning"
size="sm"
onValueChange={setBrightness}
/>
</VStack>
{/* Contrast */}
<VStack spacing="sm">
<HStack justify="space-between">
<Text variant="label">Contrast</Text>
<Text variant="caption">{contrast}%</Text>
</HStack>
<Slider
min={0}
max={200}
initialValue={contrast}
width={300}
variant="info"
size="sm"
onValueChange={setContrast}
/>
</VStack>
<HStack spacing="md">
<Button variant="outline" flex={1}>
<ButtonText>Reset</ButtonText>
</Button>
<Button variant="primary" flex={1}>
<ButtonText>Apply Changes</ButtonText>
</Button>
</HStack>
</VStack>
);
};
const AgeRangeSelector = () => {
const [ageRange, setAgeRange] = useState({ min: 25, max: 45 });
return (
<VStack spacing="md" padding="lg">
<Text variant="label">Age Range</Text>
<RangeSlider
min={18}
max={65}
initialMinValue={ageRange.min}
initialMaxValue={ageRange.max}
minDistance={1}
width={280}
variant="secondary"
size="md"
showLabels={true}
labelFormatter={(value) => `${value}y`}
onValueChange={setAgeRange}
/>
<Text style={{ textAlign: 'center' }}>
Ages {ageRange.min} - {ageRange.max} years
</Text>
</VStack>
);
};
const ControlledSlider = () => {
const sliderRef = useRef<SliderRef>(null);
const rangeSliderRef = useRef<RangeSliderRef>(null);
const resetToDefaults = () => {
sliderRef.current?.setValue(50);
rangeSliderRef.current?.setValues(20, 80);
};
const getCurrentValues = () => {
const singleValue = sliderRef.current?.getValue();
const rangeValues = rangeSliderRef.current?.getValues();
console.log('Single:', singleValue);
console.log('Range:', rangeValues);
};
return (
<VStack spacing="lg">
<Slider
ref={sliderRef}
min={0}
max={100}
initialValue={50}
width={280}
/>
<RangeSlider
ref={rangeSliderRef}
min={0}
max={100}
initialMinValue={20}
initialMaxValue={80}
width={280}
/>
<HStack spacing="md">
<Button onPress={resetToDefaults}>
<ButtonText>Reset</ButtonText>
</Button>
<Button onPress={getCurrentValues}>
<ButtonText>Get Values</ButtonText>
</Button>
</HStack>
</VStack>
);
};
const CustomStyledSlider = () => {
return (
<VStack spacing="lg">
<Slider
min={0}
max={100}
initialValue={30}
width={300}
trackColor="#E5E7EB"
activeTrackColor="#3B82F6"
thumbColor="#FFFFFF"
thumbSize={24}
trackStyle={{
height: 6,
borderRadius: 3,
}}
thumbStyle={{
borderWidth: 3,
borderColor: '#3B82F6',
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.25,
shadowRadius: 4,
elevation: 5,
}}
labelStyle={{
fontSize: 14,
fontWeight: '600',
color: '#374151',
}}
showLabel={true}
/>
</VStack>
);
};
const SteppedSliders = () => {
const [rating, setRating] = useState(3);
const [difficulty, setDifficulty] = useState(5);
return (
<VStack spacing="xl" padding="lg">
{/* Rating Slider */}
<VStack spacing="sm">
<Text variant="label">Rating: {rating} stars</Text>
<Slider
min={1}
max={5}
step={1}
initialValue={rating}
width={250}
variant="warning"
size="lg"
showLabel={true}
labelFormatter={(value) => `${value}`}
onValueChange={setRating}
/>
</VStack>
{/* Difficulty Slider */}
<VStack spacing="sm">
<Text variant="label">Difficulty: {difficulty}/10</Text>
<Slider
min={1}
max={10}
step={1}
initialValue={difficulty}
width={250}
variant="error"
size="md"
showLabel={true}
onValueChange={setDifficulty}
/>
</VStack>
</VStack>
);
};
// Default spring configuration
const DEFAULT_SPRING_CONFIG = {
damping: 15,
mass: 1,
stiffness: 150,
};
// Custom spring for smoother animation
const SMOOTH_SPRING = {
damping: 20,
mass: 1,
stiffness: 100,
};

User Experience

  • Use appropriate step values for the data type (e.g., 0.1 for decimals, 1 for integers)
  • Provide visual feedback with labels for precise value selection
  • Consider the minimum distance for range sliders to prevent overlapping thumbs

Performance

  • Use runOnJS sparingly and only for necessary callbacks
  • Avoid heavy computations in onValueChange callbacks
  • Consider debouncing API calls triggered by slider changes

The slider components use React Native Reanimated worklets for optimal performance:

// Worklet functions run on the UI thread
const clamp = (value: number, min: number, max: number): number => {
'worklet';
return Math.min(Math.max(value, min), max);
};
const snapToStep = (value: number, step: number, min: number): number => {
'worklet';
const snapped = Math.round((value - min) / step) * step + min;
return snapped;
};

These functions ensure smooth, responsive interactions by performing calculations on the UI thread without JavaScript bridge overhead.