A community-supported, open-source React Native library for the Mapbox Navigation SDK v3 for iOS and Android, built on @rnmapbox/maps.
It requests routes, runs an active-guidance session and reports route progress, and the Navigation SDK draws the route line, the puck and the navigation camera on a regular @rnmapbox/maps MapView. <NavigationView> shows the SDK's full-screen turn-by-turn UI instead.
Status: early alpha, published to npm under the
nexttag. APIs may change between releases.
API reference: rnmapbox.github.io/docs/navigation/api.
- React Native 0.79+ with the New Architecture.
@rnmapbox/maps>= 10.3.11 (peer dependency). Navigation maps plug into its map view factory hooks (mapViewImpl) in the form they have since 10.3.11 (rnmapbox/maps#4374, #4375); older versions don't build with it.- iOS: React Native's minimum deployment target (15.1 for React Native 0.84; the Navigation SDK itself needs iOS 14). Mapbox Navigation SDK for iOS 3.23.1 (
MapboxNavigationCoreandMapboxNavigationUIKit), added as a Swift package. - Android:
minSdkVersion24,compileSdkVersion36. Mapbox Navigation SDK for Android 3.23.1 (com.mapbox.navigationcore:navigation,ui-mapsandui-components). - A Mapbox account and a public access token (
pk.…). Neither platform needs a secret downloads token: the Android release artifacts and the iOS Swift package binaries download without one.
Navigation is billed per trip or MAU to the access token you pass, on top of Maps usage. See Navigation SDK pricing.
yarn add @rnmapbox/navigation@next @rnmapbox/mapsUse a development build; Expo Go doesn't include the native SDKs. Add both config plugins to app.json:
{
"expo": {
"plugins": [
["@rnmapbox/maps", { "RNMapboxMapsVersion": "11.23.1" }],
"@rnmapbox/navigation"
]
}
}Then run npx expo prebuild --clean. The @rnmapbox/navigation plugin only changes iOS:
- sets
$RNMapboxMapsSwiftPackageManager = { link: false }in the Podfile, see iOS; - adds the Navigation SDK's binary frameworks to
rnmapbox-maps' framework search paths, which Xcode 26 needs with React Native 0.88+; - sets
NSLocationWhenInUseUsageDescription, unlesslocationWhenInUsePermissionisfalse.
On Android the @rnmapbox/maps plugin already adds the Mapbox Maven repository, and the Navigation SDK's manifest adds the location, foreground service and notification permissions.
Options: RNMapboxNavigationVersion sets the Navigation SDK version on both platforms (set the maps plugin's RNMapboxMapsVersion to the matching 11.N), and locationWhenInUsePermission sets the usage string.
The Navigation SDK v3 is only distributed as a Swift package. @rnmapbox/navigation links it, and through it MapboxMaps, into the app.
@rnmapbox/maps must only compile against that MapboxMaps (its compile-only Swift Package Manager mode). Otherwise the app links two copies of MapboxMaps and fails with duplicate symbols.
# ios/Podfile, before `target`
$RNMapboxMapsSwiftPackageManager = { link: false }
target 'YourApp' do
# ...
pre_install do |installer|
$RNMapboxMaps.pre_install(installer)
end
post_install do |installer|
react_native_post_install(installer, ...)
$RNMapboxMaps.post_install(installer)
end
endpod install adds MapboxCommon, MapboxCoreMaps, MapboxNavigationNative and Turf to the frameworks CocoaPods embeds; Xcode doesn't embed the dynamic frameworks of a package that's only linked through a pod. It uses the helper in @rnmapbox/maps' ios/rnmapbox_spm.rb.
Add NSLocationWhenInUseUsageDescription to your Info.plist. For <NavigationView>, also add audio to UIBackgroundModes: the SDK's voice controller asserts on it in debug builds.
Podfile options (set them before use_native_modules!):
# Navigation SDK version, defaults to package.json `mapboxNavigation.ios`. Nav 3.N pins Maps 11.N.
$RNMapboxNavigationVersion = '3.23.1'
# Don't add the Swift package; add mapbox-navigation-ios (products MapboxNavigationCore and
# MapboxNavigationUIKit) yourself.
$RNMapboxNavigationSwiftPackageManager = 'manual'@rnmapbox/maps' package requirement comes from $RNMapboxMapsVersion (default ~> 11.23.1), and must be compatible with the Maps version the Navigation SDK pins, otherwise Xcode reports a package resolution error. With a newer Navigation SDK, set $RNMapboxMapsVersion = '= 11.N.x' to match.
Add the Mapbox Maven repository, the same one @rnmapbox/maps uses.
// android/build.gradle
buildscript {
ext {
// Nav 3.23.1 pins Maps 11.23.1. Both packages must resolve the same Maps.
RNMapboxMapsVersion = '11.23.1'
// Optional, defaults to package.json `mapboxNavigation.android`.
RNMapboxNavigationVersion = '3.23.1'
}
}
allprojects {
repositories {
maven { url 'https://api.mapbox.com/downloads/v2/releases/maven' }
}
}@rnmapbox/navigation adds a strict constraint on the Maps version its Navigation SDK is built against (Nav 3.N → Maps 11.N, or RNMapboxNavigationMapsVersion), so a different RNMapboxMapsVersion fails dependency resolution instead of silently mixing versions.
With targetSdkVersion 35 or higher, @rnmapbox/maps uses the -ndk27 Maps artifacts (16 KB pages). @rnmapbox/navigation applies the same rule to the Navigation artifacts, so both use the same Maps and Common artifacts. Keep targetSdkVersion in the root project ext, where both libraries read it.
| @rnmapbox/navigation | Navigation SDK | Maps SDK (pinned by Nav) |
|---|---|---|
| 0.1.x | 3.23.1 | 11.23.1 |
Navigation SDK 3.N depends on Maps SDK 11.N exactly. On Android, set RNMapboxMapsVersion to the matching 11.N. On iOS, @rnmapbox/maps' Swift package requirement must allow it.
Request a route, show it on a navigation map, and start simulated guidance:
import { useEffect, useState } from 'react';
import { Text, View } from 'react-native';
import Mapbox, { Camera, MapView } from '@rnmapbox/maps';
import Navigation, {
NAVIGATION_MAP_VIEW_IMPL,
NavigationViewport,
type Position,
type Route,
type RouteProgressEvent,
} from '@rnmapbox/navigation';
const ACCESS_TOKEN = 'pk....';
Mapbox.setAccessToken(ACCESS_TOKEN);
Navigation.setAccessToken(ACCESS_TOKEN);
const ORIGIN: Position = [-122.39726, 37.79205]; // [longitude, latitude]
const DESTINATION: Position = [-122.39351, 37.79554];
export function NavigationScreen() {
const [progress, setProgress] = useState<RouteProgressEvent>();
useEffect(() => {
const subscription = Navigation.addRouteProgressListener(setProgress);
let route: Route | undefined;
let cancelled = false;
Navigation.requestRoutes({ waypoints: [ORIGIN, DESTINATION] })
.then(({ main, alternatives }) => {
alternatives.forEach((alternative) => alternative.release());
route = main;
if (cancelled) {
main.release();
return;
}
// Switches the camera to 'following'.
return Navigation.startActiveGuidance(main, { simulate: true });
})
.catch((error) => console.warn(error));
return () => {
cancelled = true;
subscription.remove();
Navigation.stopNavigation();
route?.release();
};
}, []);
return (
<View style={{ flex: 1 }}>
<MapView style={{ flex: 1 }} mapViewImpl={NAVIGATION_MAP_VIEW_IMPL}>
<Camera defaultSettings={{ centerCoordinate: ORIGIN, zoomLevel: 14 }} />
{/* Keeps the puck and the route clear of UI drawn over the map. */}
<NavigationViewport
padding={{ top: 16, bottom: 120, left: 16, right: 16 }}
/>
</MapView>
{progress && (
<Text>
{progress.maneuverInstruction} in{' '}
{Math.round(progress.stepDistanceRemaining)} m
</Text>
)}
</View>
);
}Navigation.previewRoute(route) shows a route on navigation maps before guidance starts, and Navigation.setCameraState('overview') fits the rest of the route.
Navigation is the default export and also a named export.
| Function | Description |
|---|---|
setAccessToken(token) |
Must be called before any request. |
getSdkVersion() |
Version of the native Navigation SDK. |
getDefaultDistanceUnit() |
'metric' or 'imperial', from the device's region and measurement settings. |
requestRoutes(options) |
{ main, alternatives } routes through options.waypoints; also profile, alternatives, exclude, language, units. |
previewRoute(route | null) |
Shows a route on navigation maps, or removes it. |
startActiveGuidance(route, { simulate }) |
Starts guidance along route; simulate drives it with simulated locations. |
stopNavigation() |
Stops guidance and clears the maps. |
getRouteStats() |
Counts of live, released and garbage-collected routes. |
setCameraState(state) / getCameraState() |
'following', 'overview' or 'idle'. |
addCameraStateListener(listener) |
Camera state changes. |
addRouteProgressListener(listener) |
Distance, duration and fraction remaining, and the next maneuver's banner. |
addLocationListener(listener) |
Snapped location, road name and speed limit. |
addArriveListener(listener) |
Waypoint arrivals. |
addOffRouteListener(listener) |
Leaving and rejoining the route. |
addRouteChangeListener(listener) |
New Route after a reroute or a switch to an alternative. |
addVoiceInstructionListener(listener) |
Voice instruction text and SSML. |
setVoiceMuted(muted) / isVoiceMuted() |
Mutes the SDK's voice instructions. |
NAVIGATION_MAP_VIEW_IMPL |
mapViewImpl value for navigation maps. |
<NavigationViewport padding /> |
Per-map camera padding. |
<NavigationView route | waypoints /> |
Full-screen navigation UI; see NavigationView. |
<RNNavigationView route | waypoints /> |
The same UI in React Native; see RNNavigationView. |
requestRoutes resolves to { main: Route, alternatives: Route[] }. A Route is a JSI host object backed by the SDK route (NavigationRoutes on iOS, NavigationRoute on Android):
interface Route {
readonly distance: number; // meters
readonly expectedTravelTime: number; // seconds
readonly geometry: LineString; // GeoJSON, a new object on every read
readonly legs: RouteLeg[]; // { distance, expectedTravelTime, stepCount, summary, steps }
readonly isReleased: boolean;
release(): void;
}- The getters read a copy taken when the route was created. They're synchronous and don't call into the SDK.
release()frees the native route. Calling it again does nothing. Release the routes you don't keep, such as unused alternatives; the garbage collector releases the rest eventually, which can take long because it doesn't see the native memory.- After
release(), reading anything butisReleasedthrows anErrorwithcode: 'route_released', andstartActiveGuidanceandpreviewRoutereject or throw it. - Guidance and the preview keep their own reference to the native route until
stopNavigation. Releasing a route they use is safe and doesn't change them. Releasing can't throw, so a route the GC finalizes mid-guidance behaves the same as one released by hand. - Each leg's
stepsare{ instruction, distance, expectedTravelTime, maneuverType, maneuverModifier, name }. getRouteStats()returns{ live, released, collected }counts, for leak checks.- Routes don't survive a JS reload.
<MapView mapViewImpl={NAVIGATION_MAP_VIEW_IMPL}> is a regular @rnmapbox/maps map. Its props, children and events keep working. The Navigation SDK also draws the route line and the puck on it and drives its camera:
- On iOS the map is wrapped in the SDK's
NavigationMapView. - On Android the SDK's route line, location puck and
NavigationCameraare bound to the map.
There's one navigation session per app. Every navigation map shows its route, puck and camera state. Without guidance the maps show the route passed to previewRoute.
<NavigationViewport> configures the map it's a child of. Its padding is in points from the map's edges and ignores the safe area. It applies to following, overview and the route preview. Without it the SDK defaults apply: the safe area plus a small margin on iOS, and no padding on Android.
| State | Camera |
|---|---|
following |
Follows the puck, tilted and rotated with the route. Set by startActiveGuidance. |
overview |
Fits the rest of the route. |
idle |
Left alone, for the user's gestures and @rnmapbox/maps Camera. |
The state switches to idle when:
- a pan, pinch, rotate, pitch or double-tap gesture happens on any navigation map;
- an
@rnmapbox/mapsCameramoves the map from its props orsetCamera.defaultSettingsdoesn't count; stopNavigationis called.
addCameraStateListener reports every change, and getCameraState returns the current state. Call setCameraState('following') to recenter.
- On Android a
Cameramove withanimationDuration: 0doesn't switch the state, and the navigation camera takes the map back at the next location update. Animate the move, or callsetCameraState('idle')first. CamerafollowUserLocationand the navigation camera both drive the map. Don't combine them.
After stopNavigation, the route and the puck are removed and the camera eases back to north-up, without tilt or padding.
addRouteProgressListener: distances and durations remaining, and the banner of the next maneuver:maneuverInstruction,maneuverType,maneuverModifier,secondaryInstruction,subInstruction,lanes({ indications, valid, activeDirection }, left to right),legIndexandstepIndex.addLocationListener: the location snapped to the road or route, withspeed,course,roadNameandspeedLimit({ speed, unit: 'km/h' | 'mph', sign: 'mutcd' | 'vienna' }) where known.addArriveListener:{ waypointIndex, isFinalDestination }when a waypoint is reached.addOffRouteListener:truewhen the user leaves the route,falseonce guidance follows a route again.addRouteChangeListener:{ route, reason }when guidance switches routes on its own,reasonbeingrerouteoralternative.routeis a newRoute; release it when you're done. The previous route stays valid until released.addVoiceInstructionListener:{ text, ssml, distanceAlongStep }when an instruction is due, for apps doing their own text to speech.
The SDK speaks instructions during any guidance. setVoiceMuted(true) mutes it, including in <NavigationView>.
<NavigationView> is a full-screen navigation UI: NavigationViewController from MapboxNavigationUIKit on iOS, and an equivalent built from the Navigation SDK UI components on Android (maneuver banner with lanes and shields, speed limit, trip progress, mute, overview, recenter and end buttons).
import { NavigationView } from '@rnmapbox/navigation';
<NavigationView
style={{ flex: 1 }}
waypoints={[[-122.39726, 37.79205], { coordinate: [-122.39351, 37.79554], name: 'Ferry Building' }]}
simulate
mute={muted}
onRouteProgress={(event) => console.log(event.distanceRemaining)}
onArrive={({ waypointIndex, isFinalDestination }) => {}}
onCancel={() => close()}
onError={({ code, message }) => {}}
/>| Prop | Description |
|---|---|
route |
A Route from requestRoutes. Keep it unreleased while it's passed to the view. |
waypoints |
Used without route: the view requests a route through them and releases it on unmount. |
simulate |
Drives the route with simulated locations. Read when guidance starts. |
mute |
Mutes voice instructions. The view's own mute button also toggles them. |
onRouteProgress |
Same event as addRouteProgressListener. |
onArrive |
{ waypointIndex, isFinalDestination }; waypointIndex counts the origin as 0. |
onCancel |
The user ended the trip. Guidance has stopped; unmount the view. |
onError |
{ code, message }, e.g. route_request_failed, route_released, start_failed. |
It uses the app's single navigation session:
- Mounting it, or giving it a new route, starts guidance along that route and replaces any other active guidance. Navigation maps show its route and puck too.
- Unmounting it, or the user ending the trip, stops guidance.
addRouteProgressListenerandaddCameraStateListeneralso report its guidance.- Show one
NavigationViewat a time. On iOS it's embedded as a child of the nearest view controller.
<RNNavigationView> is the same kind of navigation UI written in React Native on top of the API above: a navigation map plus a maneuver banner with road shields, exit numbers and lanes, a speed limit sign, the road name, ETA and remaining time and distance, mute, overview/recenter and end buttons, and an arrival card. It takes the same props as <NavigationView>, plus:
distanceUnit:'metric'or'imperial', for the distances it shows and the voice instructions of a route it requests throughwaypoints. Defaults togetDefaultDistanceUnit().appearance:'auto'(default) switches between'day'and'night'at sunrise and sunset at the user's location; the UI and the Standard style's light preset follow it.styleURL, defaulting to Mapbox Standard. With another style,appearanceonly changes the UI.children, rendered inside itsMapView.
With waypoints it also requests alternative routes. Navigation maps draw the alternatives of the active guidance, and tapping one switches guidance to it, which addRouteChangeListener reports with reason: 'alternative'.
Use it as is, or copy it as a starting point for your own UI.
example/ (React Native 0.84.1) has a navigation map with route preview, simulated guidance and camera states, a "Route lifecycle" screen with Route checks and live counts, a "NavigationView" screen with simulated guidance, and a "RNNavigationView" screen with the React Native UI, simulated or on the device's location, and a log of arrival, off-route, reroute and voice events.
It reads your Mapbox access token from example/accesstoken (gitignored), the same way the @rnmapbox/maps example does:
nvm use # Node 22.16.0
echo "pk...." > example/accesstoken
yarn # writes example/env.json from accesstoken
yarn example start
yarn example ios # pod install runs automatically
yarn example androidThe Expo example uses the same token; expo-example/accesstoken overrides it:
cd expo-example
npx expo prebuild --clean
npx expo run:ios
npx expo run:android- No explicit offline routing API. Requests use the SDK's default hybrid router, which only falls back to onboard routing with routing tiles already on the device.
- Android
<NavigationView>has no end-of-route or feedback UI, no day/night styles, no road name label, and doesn't show reroutes as such. <RNNavigationView>draws road shields from the Directions API's shield images, not the Mapbox designed shields the native banners use.- The Expo config plugin is only tested with Expo SDK 58.
@rnmapbox/mapsLocationPuckandUserLocationdon't work on navigation maps: the navigation puck replaces the map's location provider.- One navigation session for all navigation maps. Per-map routes or cameras aren't supported.
- Route progress events already on their way to JS still arrive after
stopNavigation. Ignore them if you need to. - Not tested yet: live (non-simulated) guidance on a device, release builds, several navigation maps at once, and unmounting a map during guidance.
yarn lint
yarn typecheck
yarn test
yarn prepare # builds lib/ with react-native-builder-bob
yarn docs # API reference into docs/api/, see belowThe API reference is generated from the JSDoc in src/ with TypeDoc and typedoc-plugin-markdown. On each published release, .github/workflows/update-docs.yml regenerates it from the release tag into docs/navigation/api/ of rnmapbox/maps-docs.
See CONTRIBUTING.md.
@rnmapbox/navigation is MIT licensed.
- The Mapbox Navigation SDKs for iOS and Android it builds on are proprietary. Their use is governed by the Mapbox Terms of Service and Navigation SDK pricing. They're dependencies and aren't redistributed here.
- You need a Mapbox account and access token to use them.
- This is a community-maintained package, not an official Mapbox product.