Save products you love by clicking the heart icon.
We use privacy-friendly analytics (Plausible + Umami) to understand how visitors use this site. No analytics are loaded without your consent. Privacy Policy
WebGPU hit Baseline, Safari finally shipped WebXR, AI-assisted XR development landed from Meta and Google, and adoption surged 40%. A no-hype look at the immersive web ecosystem in mid-2026.
The WebXR ecosystem in 2026 spans a wide range of devices — from Meta Quest 3 and Apple Vision Pro to handheld mobile AR and desktop VR headsets. Each platform has unique capabilities, input methods, and performance profiles. Building cross-platform WebXR requires a progressive enhancement approach, not a one-size-fits-all solution.
| Device | Input | Tracking | Performance | WebXR Support |
|---|---|---|---|---|
| Meta Quest 3/3S | Touch controllers, hands | Inside-out 6DOF | Mid (XR2 Gen 2) | Full WebXR |
| Apple Vision Pro | Eyes, hands, voice | Inside-out, eye tracking | High (M2 + R1) | WebXR via Safari 18 |
| Mobile AR (iOS/Android) | Touch screen | SLAM | Varies (device GPU) | WebXR AR only |
| PlayStation VR2 | PSVR2 controllers | Inside-out | High | WebXR via browser |
| Desktop VR (Index, etc.) | Motion controllers | Lighthouse/inside-out | High | Full WebXR |
Don't assume capabilities — detect them at runtime:
async function detectXRCapabilities() {
if (!navigator.xr) return { supported: false, reason: 'No WebXR' }
const supportedModes = {
inline: await navigator.xr.isSessionSupported('inline'),
immersiveVR: await navigator.xr.isSessionSupported('immersive-vr'),
immersiveAR: await navigator.xr.isSessionSupported('immersive-ar'),
}
// Detect specific features
const features = {
handTracking: supportedModes.immersiveVR && 'hand-tracking' in XRSession.prototype,
hitTest: 'XRHitTestSource' in window,
anchors: 'XRAnchor' in window,
depthSensing: 'XRDepthInformation' in window,
layers: 'XRCompositionLayer' in window,
eyeTracking: 'XREye' in window,
}
return { supportedModes, features }
}
Layer capabilities from basic to advanced:
function XRApp() {
const [capabilities, setCapabilities] = useState<XRCapabilities | null>(null)
useEffect(() => {
detectXRCapabilities().then(setCapabilities)
}, [])
if (!capabilities) return <LoadingScreen />
if (!capabilities.supportedModes.immersiveVR && !capabilities.supportedModes.immersiveAR) {
return <Fallback3DView />
}
return (
<XRRenderer capabilities={capabilities}>
<SpatialUI inputMethod={detectInputMethod(capabilities)} />
<SceneGraph quality={selectQuality(capabilities)} />
</XRRenderer>
)
}
Abstract input so UI logic doesn't depend on device:
type XRInputEvent = {
position: THREE.Vector3
direction?: THREE.Quaternion
type: 'select' | 'squeeze' | 'pinch' | 'gaze' | 'touch'
source: 'controller' | 'hand' | 'gaze' | 'screen'
}
function createInputManager(capabilities: XRCapabilities) {
const input$ = new Subject<XRInputEvent>()
if (capabilities.features.handTracking) {
setupHandTracking(input$)
}
if (capabilities.supportedModes.immersiveVR) {
setupControllers(input$)
}
// Always add gaze fallback
setupGazeInput(input$)
return input$
}
Quest has excellent WebXR support but limited GPU. Key strategies:
// Quest-specific optimization: fixed foveated rendering
const xrSession = await navigator.xr.requestSession('immersive-vr', {
optionalFeatures: ['foveation'],
})
if (xrSession.updateRenderState) {
xrSession.updateRenderState({
foveationLevel: 0.5, // 0=off, 1=maximum
})
}
Use passthrough AR on Quest 3 for mixed reality experiences:
const session = await navigator.xr.requestSession('immersive-ar', {
optionalFeatures: ['passthrough'],
})
Vision Pro Safari 18 supports WebXR with unique constraints:
// Vision Pro: use eye tracking for precision targeting
const eyeGazeSource = await session.requestReferenceSpace('local')
const eyeTracker = await session.requestAnimationFrame((time, frame) => {
const pose = frame.getPose(eyeGazeSpace, referenceSpace)
if (pose) highlightTarget(pose.transform.position)
})
// Vision Pro: handle direct touch input
element.addEventListener('touchstart', (e) => {
// Vision Pro reports touches as pointer events from eye + pinch
})
Key Vision Pro considerations:
Mobile AR is the widest reach but most constrained:
if (sessionMode === 'immersive-ar' && isMobileDevice()) {
// Mobile AR: flat surface placement is the primary interaction
session.requestHitTestSource({ space: viewerSpace })
// Limit draw calls to ~30
// Use half-resolution rendering
gl.setSize(window.innerWidth / 2, window.innerHeight / 2)
}
Scale rendering quality based on device capability:
function selectQuality(device: XRCapabilities): QualitySettings {
const gpuTier = detectGPUTier()
if (gpuTier === 'high') {
return { pixelRatio: 1, shadows: true, postProcessing: true, msaa: 4 }
}
if (gpuTier === 'medium') {
return { pixelRatio: 0.75, shadows: false, postProcessing: false, msaa: 2 }
}
// Low-end (mobile)
return { pixelRatio: 0.5, shadows: false, postProcessing: false, msaa: 1 }
}
Test across devices in CI:
jobs:
webxr-test:
strategy:
matrix:
device: [quest-3, vision-pro, mobile-ios, mobile-android]
steps:
- uses: actions/checkout@v4
- run: npm ci
- run: npm run build
- uses: WebXR-Test/action@v2
with:
device: ${{ matrix.device }}
url: https://staging.example.com/xr-app
tests: tests/webxr/
Always provide a non-XR experience:
function Fallback3DView() {
return (
<div className="relative h-screen w-full">
<canvas ref={canvasRef} /> {/* Three.js fallback */}
<div className="absolute bottom-8 left-1/2 -translate-x-1/2">
<p className="text-sm text-gray-500">
WebXR not supported on this device.
View the 3D scene in your browser instead.
</p>
</div>
</div>
)
}
Before shipping cross-platform XR:
Cross-platform WebXR in 2026 means supporting a wider range of devices than ever. Test on real hardware, not just emulators.