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
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)}/>
<RangeSlider min={0} max={100} initialMinValue={20} initialMaxValue={80} onValueChange={(values) => console.log(values.min, values.max)}/>
<Slider min={0} max={100} initialValue={25} showLabel={true} labelFormatter={(value) => `${value}%`} onValueChange={handleValueChange}/>
<Slider min={0} max={10} step={0.5} variant="success" size="lg" width={280} initialValue={5} onValueChange={handleChange}/>
Prop | Type | Default | Description |
---|---|---|---|
min | number | 0 | Minimum slider value |
max | number | 100 | Maximum slider value |
step | number | 1 | Value increment step |
initialValue | number | min | Initial slider value |
width | number | 300 | Slider track width |
height | number | auto | Container height (based on size) |
disabled | boolean | false | Disable slider interactions |
variant | ComponentVariant | 'default' | Visual style variant |
size | ComponentSize | 'md' | Slider size (xs, sm, md, lg, xl) |
trackColor | string | auto | Inactive track color |
activeTrackColor | string | auto | Active track color |
thumbColor | string | auto | Thumb background color |
thumbSize | number | auto | Thumb diameter (based on size) |
showLabel | boolean | false | Show value label on drag |
labelFormatter | (value: number) => string | value.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 |
Prop | Type | Default | Description |
---|---|---|---|
min | number | 0 | Minimum slider value |
max | number | 100 | Maximum slider value |
step | number | 1 | Value increment step |
initialMinValue | number | min | Initial minimum value |
initialMaxValue | number | max | Initial maximum value |
minDistance | number | 0 | Minimum distance between thumbs |
width | number | 300 | Slider track width |
height | number | auto | Container height (based on size) |
disabled | boolean | false | Disable slider interactions |
variant | ComponentVariant | 'default' | Visual style variant |
size | ComponentSize | 'md' | Slider size (xs, sm, md, lg, xl) |
trackColor | string | auto | Inactive track color |
activeTrackColor | string | auto | Active track color |
thumbColor | string | auto | Thumb background color |
thumbSize | number | auto | Thumb diameter (based on size) |
showLabels | boolean | false | Show value labels on drag |
labelFormatter | (value: number) => string | value.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 |
Variant | Description | Use Case |
---|---|---|
default | Standard theme colors | General purpose sliders |
primary | Primary brand colors | Important value selections |
secondary | Secondary brand colors | Secondary controls |
success | Success/positive colors | Positive range selections |
error | Error/danger colors | Warning or limit controls |
warning | Warning colors | Caution range selections |
info | Information colors | Informational controls |
outline | Transparent active track | Minimal design preference |
filled | Filled appearance | Enhanced visual prominence |
ghost | Subtle appearance | Minimal visual impact |
Size | Track Height | Thumb Size | Container Height |
---|---|---|---|
xs | 2px | 14px | 32px |
sm | 3px | 16px | 36px |
md | 4px | 20px | 40px |
lg | 5px | 24px | 44px |
xl | 6px | 28px | 48px |
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 configurationconst DEFAULT_SPRING_CONFIG = { damping: 15, mass: 1, stiffness: 150,};
// Custom spring for smoother animationconst SMOOTH_SPRING = { damping: 20, mass: 1, stiffness: 100,};
// Thumb scales to 1.2x when draggingconst thumbAnimatedStyle = useAnimatedStyle(() => { const scale = isDragging.value ? 1.2 : 1; return { transform: [{ scale: withSpring(scale) }], };});
// Labels fade in/out during interactionconst labelAnimatedStyle = useAnimatedStyle(() => { const opacity = isDragging.value ? 1 : 0; return { opacity: withSpring(opacity), };});
User Experience
Performance
runOnJS
sparingly and only for necessary callbacksonValueChange
callbacksThe slider components use React Native Reanimated worklets for optimal performance:
// Worklet functions run on the UI threadconst 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.