ViroXRSceneNavigator
A component for handling AR and VR scenes from a single navigator across platforms.
Introduced in ViroReact 2.55.0
ViroXRSceneNavigator is ViroReact's cross-reality entry point. It is a single component that, at runtime, picks the right underlying navigator for the device it's running on — mounting ViroARSceneNavigator inline on iOS and non-Quest Android, or launching VRActivity and forwarding all navigator operations to ViroVRSceneNavigator on Meta Quest. From your app's perspective there is one component, one ref, and one set of props; the platform branching is handled internally.
This is the component you should reach for whenever you want a single codebase to deliver AR on smartphones and VR on Quest headsets. Pass arInitialScene and vrInitialScene for per-platform scenes, or initialScene as shorthand when the same scene component works on both.
Platform compatibility
| Platform | Supported | Notes |
|---|---|---|
| iOS | ✅ | Renders ViroARSceneNavigator inline (ARKit) |
| Android | ✅ | Renders ViroARSceneNavigator inline (ARCore) |
| HorizonOS | ✅ | Launches VRActivity via OpenXR; component itself renders null in the panel |
| visionOS | ❌ | Not currently supported |
Required Expo / React Native versions
ViroXRSceneNavigator enforces the VR floor at runtime — on Quest hardware with React Native below the supported version, it throws an actionable error and refuses to launch VR. AR continues to work on the lower floor.
| Path | Minimum Expo SDK | Minimum React Native |
|---|---|---|
| AR (iOS / non-Quest Android) | 54 | 0.81 |
| VR (Meta Quest) | 55 | 0.83 |
If you only need AR, you can stay on Expo 54. If your app uses VR on Quest, upgrade to Expo 55 / RN 0.83. See the Meta Quest setup guide for full configuration steps, including the xRMode: ["QUEST"] plugin option, questAppId, and VRActivity generation.
Basic example
import * as React from "react";
import { StyleSheet } from "react-native";
import { ViroXRSceneNavigator } from "@reactvision/react-viro";
import { MyARScene } from "./MyARScene";
import { MyVRScene } from "./MyVRScene";
export default function App() {
return (
<ViroXRSceneNavigator
arInitialScene={{ scene: MyARScene }}
vrInitialScene={{ scene: MyVRScene }}
style={StyleSheet.absoluteFill}
/>
);
}
On iOS / Android phones this mounts MyARScene inside ViroARSceneNavigator. On Quest, VRActivity is launched with MyVRScene as its initial scene, and the panel-side component renders null.
Single scene across AR and VR
If you want one scene component to run on both modalities, pass initialScene instead of the platform-specific pair:
<ViroXRSceneNavigator
initialScene={{ scene: MySharedScene }}
style={StyleSheet.absoluteFill}
/>
The catch is that the two native renderers expect different scene roots: the AR path needs ViroARScene as the root (it's the bridge to ARKit/ARCore, plane detection, hit testing, camera feed) and the VR path on Quest needs ViroScene as the root. A plain ViroScene root will render nothing on AR; a ViroARScene root will render nothing on Quest.
The recommended pattern for a single shared scene component is therefore to branch at the root inside the component itself using the isQuest utility:
import { isQuest, ViroScene, ViroARScene, ViroAmbientLight, ViroBox } from "@reactvision/react-viro";
export function MySharedScene() {
const children = (
<>
<ViroAmbientLight color="#ffffff" intensity={1000} />
<ViroBox position={[0, 0, -2]} scale={[0.3, 0.3, 0.3]} />
</>
);
return isQuest ? <ViroScene>{children}</ViroScene> : <ViroARScene>{children}</ViroARScene>;
}
This is the same pattern StudioARScene uses internally to render Studio-authored content unmodified on both AR and VR.
When the user exits VR (B button, programmatic exitVRScene(), or system kill), the panel-side ViroXRSceneNavigator will still be rendering null and your screen will be blank. Always wire onExitViro to navigate away:
<ViroXRSceneNavigator
vrInitialScene={{ scene: MyVRScene }}
onExitViro={() => navigation.goBack()}
/>
Props
| Prop | Type | Default | Description |
|---|---|---|---|
initialScene | { scene: () => JSX.Element } | — | Scene used on both AR and VR when no platform-specific scene is provided. The scene component should branch its root between ViroARScene (AR) and ViroScene (Quest VR) using isQuest — see Single scene across AR and VR. Most apps pass arInitialScene and vrInitialScene instead. |
arInitialScene | { scene: () => JSX.Element } | — | Scene mounted on iOS / non-Quest Android via ViroARSceneNavigator. Falls back to initialScene if not provided. |
vrInitialScene | { scene: () => JSX.Element } | — | Scene mounted on Meta Quest via ViroVRSceneNavigator inside VRActivity. Forwarded through the intent bridge rather than rendered inline. Falls back to initialScene if not provided. |
worldAlignment | "Gravity" | "GravityAndHeading" | "Camera" | "Gravity" | Forwarded to ViroARSceneNavigator (AR path). |
autofocus | boolean | true | Forwarded to ViroARSceneNavigator (AR path). |
videoQuality | "High" | "Low" | — | Forwarded to ViroARSceneNavigator (AR path). |
numberOfTrackedImages | number | — | Forwarded to ViroARSceneNavigator (AR path). |
vrModeEnabled | boolean | — | Forwarded to ViroVRSceneNavigator on Quest via the intent bridge. |
passthroughEnabled | boolean | — | Forwarded to ViroVRSceneNavigator on Quest. Enables Meta Quest passthrough. |
handTrackingEnabled | boolean | — | Forwarded to ViroVRSceneNavigator on Quest. Enables XR_FB_hand_tracking_aim. |
onExitViro | () => void | — | Fires when exitVRScene() is called — from the B button, a programmatic exit, or an in-scene exit button. Use this to navigate away once VR ends, otherwise the panel will render blank. |
viroAppProps | any | — | Arbitrary props passed through to your scene component. |
hdrEnabled | boolean | — | Renderer flag — forwarded to both AR and VR paths. |
pbrEnabled | boolean | — | Renderer flag — forwarded to both AR and VR paths. |
bloomEnabled | boolean | — | Renderer flag — forwarded to both AR and VR paths. |
shadowsEnabled | boolean | — | Renderer flag — forwarded to both AR and VR paths. |
multisamplingEnabled | boolean | — | Renderer flag — forwarded to both AR and VR paths. |
debug | boolean | — | Forwarded to both AR and VR paths. |
style | ViewStyle | — | Standard React Native ViewProps. Ignored on Quest (the component renders null there). |
ViewProps from React Native are also accepted (for example testID).
Methods
ViroXRSceneNavigator exposes a unified navigator handle through its forwarded ref. The handle is named arSceneNavigator for both paths — the name is historical, and it works on Quest as well, where every call is proxied through VRQuestNavigatorBridge to the ViroVRSceneNavigator running inside VRActivity.
const navRef = React.useRef<any>(null);
<ViroXRSceneNavigator ref={navRef} vrInitialScene={{ scene: MyVRScene }} />
// Push a new scene
navRef.current?.arSceneNavigator?.push({ scene: DetailScene });
// Pop back
navRef.current?.arSceneNavigator?.pop();
| Method | Description |
|---|---|
push(scene) | Push the given scene onto the scene stack, displaying it to the user. |
replace(scene) | Replace the top scene in the stack with the given scene. The remainder of the back history is preserved. |
jump(scene) | Move the given scene to the top of the stack, removing it from its current position. Displays it to the user. |
pop() | Pop the top-most scene off the stack, returning to the previous scene. |
popN(n) | Pop n scenes off the stack at once. popN(1) is equivalent to pop(). |
On the AR path, the underlying
ViroARSceneNavigatorinstance is exposed directly through the ref, so any additional methods documented onViroARSceneNavigator(for examplehostCloudAnchor,resolveCloudAnchor,recenterTracking) are available there too. These additional methods are AR-specific and are not bridged to Quest.
See also
Updated 4 days ago