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

PlatformSupportedNotes
iOSRenders ViroARSceneNavigator inline (ARKit)
AndroidRenders ViroARSceneNavigator inline (ARCore)
HorizonOSLaunches VRActivity via OpenXR; component itself renders null in the panel
visionOSNot 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.

PathMinimum Expo SDKMinimum React Native
AR (iOS / non-Quest Android)540.81
VR (Meta Quest)550.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

PropTypeDefaultDescription
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).
autofocusbooleantrueForwarded to ViroARSceneNavigator (AR path).
videoQuality"High" | "Low"Forwarded to ViroARSceneNavigator (AR path).
numberOfTrackedImagesnumberForwarded to ViroARSceneNavigator (AR path).
vrModeEnabledbooleanForwarded to ViroVRSceneNavigator on Quest via the intent bridge.
passthroughEnabledbooleanForwarded to ViroVRSceneNavigator on Quest. Enables Meta Quest passthrough.
handTrackingEnabledbooleanForwarded to ViroVRSceneNavigator on Quest. Enables XR_FB_hand_tracking_aim.
onExitViro() => voidFires 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.
viroAppPropsanyArbitrary props passed through to your scene component.
hdrEnabledbooleanRenderer flag — forwarded to both AR and VR paths.
pbrEnabledbooleanRenderer flag — forwarded to both AR and VR paths.
bloomEnabledbooleanRenderer flag — forwarded to both AR and VR paths.
shadowsEnabledbooleanRenderer flag — forwarded to both AR and VR paths.
multisamplingEnabledbooleanRenderer flag — forwarded to both AR and VR paths.
debugbooleanForwarded to both AR and VR paths.
styleViewStyleStandard 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();
MethodDescription
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 ViroARSceneNavigator instance is exposed directly through the ref, so any additional methods documented on ViroARSceneNavigator (for example hostCloudAnchor, resolveCloudAnchor, recenterTracking) are available there too. These additional methods are AR-specific and are not bridged to Quest.

See also