Slider
@symbiote-native/slider wraps the RNCSlider native view from
@react-native-community/slider so every
SymbioteNative adapter can render it — without importing that library’s React component body. It is
the reference implementation of the third-party native-view wrapper
pattern: the recipe to follow for any future community RN view. Its counterpart is
splash screen, which wraps a community library that ships an
imperative native module and no view at all — both are autolinked through a
react-native.config.cjs + podspec proxy, unlike the expo-modules-core packages such as
local auth, whose native code expo-modules-autolinking discovers
instead.
| OS platform | Support |
|---|---|
| iOS | ✅ live |
| Android | ✅ live |
| Framework adapter | Support |
|---|---|
| React | ✅ live |
| Vue | ✅ live |
| Angular | ✅ live |
| Svelte | ✅ live |
Installation
Section titled “Installation”pnpm add @react-native-community/slider@symbiote-native/slider itself is a workspace package (packages/slider), not yet published — add it
as a workspace dependency the same way the examples do.
import { useState } from 'react';import { Slider } from '@symbiote-native/slider/react';
export default function VolumeControl() { const [volume, setVolume] = useState(0.5);
return ( <Slider value={volume} minimumValue={0} maximumValue={1} step={0.05} onValueChange={setVolume} minimumTrackTintColor="#61dafb" thumbTintColor="#61dafb" /> );}<script setup lang="ts">import { ref } from 'vue';import { Slider } from '@symbiote-native/slider/vue';
const volume = ref(0.5);</script>
<template> <Slider v-model="volume" :minimum-value="0" :maximum-value="1" :step="0.05" minimum-track-tint-color="#42d392" thumb-tint-color="#42d392" /></template>v-model is sugar over the same value/@value-change pair — write
:value="volume" @value-change="volume = $event" instead if you need the
explicit form. See the Vue API reference.
import { Component, signal } from '@angular/core';import { Slider } from '@symbiote-native/slider/angular';
@Component({ standalone: true, imports: [Slider], template: ` <Slider [(value)]="volume" [minimumValue]="0" [maximumValue]="1" [step]="0.05" minimumTrackTintColor="#dd0031" thumbTintColor="#dd0031" /> `,})export class VolumeControl { readonly volume = signal(0.5);}[(value)] is Angular’s banana-in-a-box two-way binding over the same
value input + valueChange output pair — write [value]="volume()" (valueChange)="volume.set($event)" instead if you need the explicit form.
Slider also implements ControlValueAccessor directly, so it plugs
straight into @angular/forms — [formControl]="volumeControl" (from
ReactiveFormsModule) works the same way it does on TextInput/Switch,
no extra import needed. See the Angular API reference’s Forms
section for the full pattern.
<script lang="ts"> import { Slider } from '@symbiote-native/slider/svelte';
let volume = $state(0.5);</script>
<Slider bind:value={volume} minimumValue={0} maximumValue={1} step={0.05} minimumTrackTintColor="#ff3e00" thumbTintColor="#ff3e00"/>value is declared $bindable(), so bind:value={x} works as Svelte’s own
two-way-binding sugar — the compiler wires the sync at the call site, no
adapter plumbing involved. The explicit value={x} onValueChange={setX}
form still works too and is required if you need to see or react to every
reported value yourself; don’t pass both on the same instance, since
supplying onValueChange takes over and the bound variable stops updating
from native reports.
Custom step markers
Section titled “Custom step markers”renderStepNumber draws the library’s built-in step dots. To draw your own marker per step,
pass a StepMarker (React), fill the #stepMarker scoped slot (Vue), or pass a stepMarker
snippet (Svelte) — all three receive { stepMarked, currentValue, index, min, max }:
<Slider value={2} minimumValue={0} maximumValue={4} step={1} StepMarker={({ stepMarked }) => ( <View style={{ width: 8, height: 8, borderRadius: 4, opacity: stepMarked ? 1 : 0.4 }} /> )}/><Slider value={2} minimumValue={0} maximumValue={4} step={1}>{#snippet stepMarker({ stepMarked })}<View style={{ width: 8, height: 8, borderRadius: 4, opacity: stepMarked ? 1 : 0.4 }} />{/snippet}</Slider>Every prop below is framework-agnostic and shared verbatim by every adapter, except
StepMarker, which is per-adapter because it returns a framework element (a React component, a
Vue scoped slot, or a Svelte snippet) — see shared vs
framework-specific.
| Prop | Type | Default | Description |
|---|---|---|---|
value |
number |
0 |
The slider’s position. Uncontrolled during a drag — it doesn’t snap back to value until onSlidingComplete fires |
minimumValue / maximumValue |
number |
0 / 1 |
Lower/upper bound of the draggable range |
step |
number |
0 |
Snaps the thumb to increments of this size; 0 draws implicit steps at native resolution (iOS 1000, Android 128) |
lowerLimit / upperLimit |
number |
unbounded | Clamps the draggable range without changing minimumValue/maximumValue |
minimumTrackTintColor / maximumTrackTintColor / thumbTintColor |
IColorValue |
native default | Color of the track before/after the thumb, and of the thumb itself |
thumbImage / minimumTrackImage / maximumTrackImage / trackImage |
IImageSourceProp |
— | Replaces the corresponding native default image |
thumbSize |
number |
native default | Diameter of the thumb, in points |
disabled |
boolean |
false |
Disables dragging and dims the control to the native disabled look |
inverted |
boolean |
false |
Reverses the direction the track fills in |
tapToSeek |
boolean |
false |
Tapping anywhere on the track jumps the thumb there, instead of requiring a drag |
vertical |
boolean |
false |
Renders the slider top-to-bottom instead of left-to-right |
renderStepNumber |
boolean |
false |
Draws the built-in step-number indicator |
StepMarker (React) / #stepMarker (Vue) / stepMarker (Svelte) |
render prop / scoped slot / snippet | — | Custom per-step marker, replacing renderStepNumber’s built-in dot |
onValueChange |
(value: number) => void |
— | Fires continuously while dragging |
onSlidingStart / onSlidingComplete |
(value: number) => void |
— | Fires once when a drag begins/ends |
accessibility / aria-* / testID / style |
— | — | Pass through to the native node unchanged |
value={0}is discarded, not applied. The shared fold treatsNaNand any falsy number, including0, as “no value given”, so the native view falls back to its own initial position — mirroring the community component. A slider that must start at zero wantsminimumValue={0}and no explicitvalue.- A bare slider measures differently per platform. The iOS entry gives the wrapper a 40pt
default height and nudges the step row down 10pt; the Android entry supplies no default height at
all, leaving the native view’s intrinsic size to decide, and keeps the row at the top. Set an
explicit height through
styleif the two have to agree. - A custom
StepMarkertakes over the native thumb. With a marker present,thumbImageis not forwarded to the native view — the marker draws it — and if you pass both,thumbTintColoris forced totransparentso the marker shows through. - Crossed limits fail quietly.
lowerLimit >= upperLimitonly reaches adlog, which is off unlessDEBUGis set, so an inverted pair produces no console warning at all — just a slider that won’t move where you expect.
How the wrapper works
Section titled “How the wrapper works”@symbiote-native/slider ships zero runtime metadata for RNCSlider — the engine derives its
events and color/image processors from the library’s own codegen ViewConfig at first commit
(setNativeViewConfigSource). The package only supplies the pure JS folding the library’s React
wrapper normally does (value/limit sanitizing, the step-indicator layout) plus the native
Descriptor render, once, in packages/slider/src/core:
packages/slider/src/├── core/ # framework-agnostic: state folds, render-slider, render-steps-indicator├── register.ts # side-effect: registers RNCSlider's ViewConfig fallback + event/color processors├── react/ # React lifecycle (hooks) + descriptorToReact bridge├── vue/ # Vue lifecycle (refs/emits) + descriptorToVue bridge├── angular/ # Angular lifecycle (@Output EventEmitters) + DescriptorOutlet bridge└── svelte/ # Svelte lifecycle (runes) + the native-view bridge's descriptor-children mountEvery adapter imports ../register first (never the library’s Slider.tsx), then calls into the
same core render — the same logic/view/lifecycle split as every other SymbioteNative component (see
how it works). Wrapping a different third-party native view follows
this same recipe: register its ViewConfig fallback and processors as a side effect, then implement the
shared core render once and add a thin per-framework lifecycle bridge around it — never the library’s
own React component.