Shipnative
ShipNativeShipNative
Sign inStart building
GuideOctober 2026 · 8 min read

React Native Slider: Community Package or Custom

Search for a React Native slider and half the answers tell you to import { Slider } from 'react-native'. That stopped working years ago. Slider moved out of core into a community package, and the package works well as long as you avoid the one mistake nearly everyone makes with it: binding its value to state that changes on every frame. This guide covers the three real options, a working slider in Expo, the stutter and step bugs, and a small custom slider for when the native one cannot be styled the way your design needs.

The three options

OptionLookExpo GoBest forTrade-off
@react-native-community/sliderPlatform native✅ YesVolume, brightness, single valuesOne thumb, limited styling
@ptomasroos/react-native-multi-sliderStyled by you✅ YesPrice and age rangesSlow maintenance, JS-thread drag
Custom (Gesture Handler + Reanimated)Fully yours✅ YesDesign-led controls, vertical slidersYou own accessibility

Start with the community package. It wraps the real platform control, so it gets native feel, VoiceOver and TalkBack support, and correct behaviour inside scroll views for free. Reach for a custom slider only when you need two thumbs, a vertical layout, or a shape the native track cannot draw.

A working slider in Expo

npx expo install @react-native-community/slider
import { useState } from 'react';
import { StyleSheet, Text, View } from 'react-native';
import Slider from '@react-native-community/slider';

export default function TipSlider({ initial = 15, onSave }) {
  // Display value only. The slider owns its own position while dragging.
  const [shown, setShown] = useState(initial);

  return (
    <View style={styles.wrap}>
      <Text style={styles.label}>Tip: {shown}%</Text>
      <Slider
        value={initial}
        minimumValue={0}
        maximumValue={30}
        step={5}
        onValueChange={setShown}
        onSlidingComplete={onSave}
        minimumTrackTintColor="#fb923c"
        maximumTrackTintColor="#2a2a2a"
        thumbTintColor="#ffffff"       // Android, and iOS when no thumbImage
        accessibilityLabel="Tip percentage"
        style={styles.slider}
      />
    </View>
  );
}

const styles = StyleSheet.create({
  wrap: { marginBottom: 24 },
  label: { color: '#fff', fontSize: 15, marginBottom: 8 },
  slider: { width: '100%', height: 40 },
});

Notice what the slider is not given: the shown state. The label follows the finger through onValueChange, and the value is saved once, in onSlidingComplete, when the user lets go. That split is the whole trick to a smooth slider, and it also means you write to storage or the server once per gesture instead of sixty times a second.

Why a controlled slider stutters

// ❌ The thumb fights the finger, worst on slower Android phones
const [value, setValue] = useState(0.5);
<Slider value={value} onValueChange={setValue} />

This looks like the normal React pattern, and it is the cause of most “my slider is jittery” reports. The native control moves the thumb, sends the new value across to JavaScript, React re-renders, and sends a value back down that is already a frame or two behind the finger. The native side then snaps the thumb backwards to match. On a fast iPhone you may never see it. On a mid-range Android phone with a busy screen, the thumb visibly drags and jumps.

The fix is the pattern above: treat value as the starting position only, and change it from outside only when something other than the user moves it, such as a reset button. If you need to reset, change a key on the slider or update value once, not on every drag frame.

The step bug: 0.30000000000000004

Set step={0.1} and the label will sooner or later show 0.30000000000000004. That is JavaScript floating-point arithmetic, not the slider, but the slider is where users see it. Round before you display or store:

onValueChange={(v) => setShown(Math.round(v * 10) / 10)}

// Or avoid fractions entirely: slide 0 to 100 and divide later
<Slider minimumValue={0} maximumValue={100} step={1} />

Integer ranges are the cleaner option. Store a whole-number percentage and convert at the point of use, and the bug cannot happen.

A custom slider with Gesture Handler and Reanimated

When the design calls for a thick rounded track, a vertical control, or two thumbs, build it. The drag runs on the UI thread through Reanimated, so it stays smooth regardless of what React is doing, and you report the final value to JavaScript once. Your app root needs GestureHandlerRootView, which Expo Router templates already include.

import { StyleSheet, View } from 'react-native';
import { Gesture, GestureDetector } from 'react-native-gesture-handler';
import Animated, { runOnJS, useAnimatedStyle, useSharedValue } from 'react-native-reanimated';

const WIDTH = 280;
const THUMB = 28;

export function CustomSlider({ initial = 0.5, onChange }) {
  const x = useSharedValue(initial * WIDTH);
  const start = useSharedValue(0);

  const pan = Gesture.Pan()
    .hitSlop({ vertical: 16 })
    .onBegin(() => {
      start.value = x.value;
    })
    .onUpdate((e) => {
      x.value = Math.min(WIDTH, Math.max(0, start.value + e.translationX));
    })
    .onEnd(() => {
      runOnJS(onChange)(x.value / WIDTH);
    });

  const fill = useAnimatedStyle(() => ({ width: x.value }));
  const thumb = useAnimatedStyle(() => ({ transform: [{ translateX: x.value - THUMB / 2 }] }));

  return (
    <GestureDetector gesture={pan}>
      <View style={styles.track}>
        <Animated.View style={[styles.fill, fill]} />
        <Animated.View style={[styles.thumb, thumb]} />
      </View>
    </GestureDetector>
  );
}

const styles = StyleSheet.create({
  track: { width: WIDTH, height: 8, borderRadius: 4, backgroundColor: '#2a2a2a', justifyContent: 'center' },
  fill: { height: 8, borderRadius: 4, backgroundColor: '#fb923c' },
  thumb: { position: 'absolute', left: 0, width: THUMB, height: THUMB, borderRadius: THUMB / 2, backgroundColor: '#fff' },
});

The fixed WIDTH keeps the example short. In a real screen, measure the track with onLayout and store the width in a shared value. If you are on Reanimated 4, runOnJS still works but the newer API is scheduleOnRN from react-native-worklets. Background on how the pan gesture works is in the Gesture Handler guide.

What you give up is accessibility. The native slider announces itself and responds to swipe-up and swipe-down adjustments in VoiceOver. A custom one needs accessibilityRole="adjustable", accessibilityValue, and onAccessibilityAction handlers for increment and decrement, or screen reader users cannot use it at all.

When a slider is the wrong control

Sliders are good for values where the exact number matters less than the feel: volume, brightness, a rough budget. They are poor for precise values. Nobody wants to drag a thumb to exactly 37. If the user needs a specific number, a stepper with plus and minus buttons, or a numeric input, is faster and less frustrating. A slider with five or six steps is often better as a row of chips.

Drag feel is another thing you cannot judge from a screenshot. Describe the screen, “a settings page with a font size slider that previews the text live”, and ShipNative builds it as real React Native that runs on your phone, so you can check the drag under your own thumb before you commit to it.

Frequently Asked Questions

Does React Native have a built-in Slider?

Not any more. Slider was removed from React Native core years ago and now lives in the community package @react-native-community/slider. Importing Slider from react-native fails on any current version, which is the error most old tutorials lead people into.

Does @react-native-community/slider work in Expo Go?

Yes. It is part of the Expo Go runtime, so npx expo install @react-native-community/slider is all you need, with no development build. Using expo install rather than npm install picks the version that matches your Expo SDK.

Why does my slider thumb jump or stutter while dragging?

Usually because value is bound to state that onValueChange updates on every frame. Each update re-renders the slider with a value that is already a frame old, and the thumb fights the finger. Pass the starting value, update only the label during the drag, and save in onSlidingComplete.

How do I make a range slider with two thumbs?

The community slider has one thumb only. Your options are a JavaScript package such as @ptomasroos/react-native-multi-slider, which is long-standing but slow to get updates, so check its recent activity first, or a custom slider built on react-native-gesture-handler and Reanimated with two pan gestures.

How do I make a vertical slider?

There is no vertical prop. The common approach is rotating the slider with transform: [{ rotate: "-90deg" }] inside a container sized for the rotated shape. It works, but layout is fiddly because the slider still measures as horizontal, so a custom gesture-based slider is often simpler for vertical controls.

Can an AI app builder generate sliders for me?

Yes. Describe the control, "a tip percentage slider from 0 to 30 in steps of 5 that updates the total live", and ShipNative generates the slider, the label, and the state wiring as real React Native, then runs it on your phone so you can feel whether the drag is smooth.

→

React Native Progress Bar

The read-only cousin of a slider, with the same track and fill.

Read guide →
→

React Native Gesture Handler

The gesture system the custom slider below is built on.

Read guide →

Ship a real React Native app today

Describe, preview, and export Expo code, free to start.

Build with ShipNative
ShipNative

Build mobile apps with AI. Describe it, try it on your phone, own the code.

Features

Text to App AIApp Generator from ScreenshotPRD to Mobile App

Tools

All free toolsApp Cost CalculatorApp Name GeneratorApp Store Keyword ToolReact Native Components

Blog

All blog postsHow to Build an App Without CodingBest AI Tools for Real Mobile AppsExpo EAS App Store ChecklistLovable, Cursor & v0 for MobileBest AI App Builders in 2026React Native AI App Builder Guide
© 2026 ShipNative
PrivacyTermsFAQPricingDiscord