Skip to main content

Welcome to WebXR UI Extensions

WebXR UI Extensions gives a WebXR scene movable, resizable windows, dock regions, hand menus and a small set of controls, defined once and driven the same way on every supported engine.

Overview

The engine-free core, @realitycollective/webxr-uiextensions, owns every user-experience decision: window lifecycle, dock state, region layout, drag maths, hold-to-drag timing, hand-menu placement and the control models. It imports no 3D engine, so those decisions apply unchanged wherever they run. An engine adapter is the only place an engine name appears: it turns UIKitML, an HTML and CSS-like markup language for spatial panels, into a live panel, delivers pointer and hand input into the core, and supplies the viewer pose. Where a host genuinely cannot do something, the adapter reports it rather than staying silent: WindowHost.supportsStandalonePanels says whether a bare panel is even possible on that host, and createPanel throws where it is not.

Start with the introduction for the concepts, or jump to the quickstart below.

Requirements

  • Node 20 or newer for tooling; the packages themselves run in any modern browser with WebXR.
  • A bundler of your choice; the demos use Vite.
  • TypeScript is recommended but not required; the packages ship type declarations.

Current release: 0.1.0 on npm, resolved by a bare npm install.

Packages

PackageWhat it isPeers
@realitycollective/webxr-uiextensionsEngine-free core: window manager, dock state, region layout, drag maths, control models and the adapter contract@realitycollective/webxr-input
@realitycollective/iwsdk-uiextensionsMeta IWSDK adapter, the reference implementation, with the full feature set@iwsdk/core >=0.5.0 <0.6.0, three >=0.170.0
@realitycollective/xrblocks-uiextensionsGoogle XR Blocks and plain three.js adapter, experimentalthree >=0.170.0
@realitycollective/uix-devtoolsDeveloper-only tooling: an edit-session gate, runtime UIKitML compilation and the uix-dev CLInone; installed as a devDependency

Each adapter re-exports the whole core, so an app installs one package:

# Meta IWSDK apps (core re-exported)
npm install @realitycollective/iwsdk-uiextensions

# Google XR Blocks / plain three.js apps (core re-exported, experimental)
npm install @realitycollective/xrblocks-uiextensions three

# Writing headless logic, tests, or a new engine adapter
npm install @realitycollective/webxr-uiextensions

# Developer tooling, dev dependency only, never shipped
npm install --save-dev @realitycollective/uix-devtools

Use cases

  • a status or inventory window that follows the player, or docks into a console wall
  • a body-locked toolbar built from a dock region that follows the player
  • a hand-locked menu that shows only while a palm faces the viewer
  • a stepper, toggle or scrolling log wired into a panel with a few lines of markup
  • an in-headset live-edit loop for iterating on UIKitML without a rebuild, through uix-devtools

Quickstart

Three pieces on the reference Meta IWSDK adapter: register the adapter, describe a window, then drive it from code.

1. Register the adapter

import { World } from '@iwsdk/core';
import { registerUIExtensions, createSceneHost } from '@realitycollective/iwsdk-uiextensions';

const world = await World.create(container, { features: { spatialUI: true } });
const windows = registerUIExtensions(world);
const host = createSceneHost(world);

World is IWSDK's entity-component-system container; registerUIExtensions registers every system the adapter needs and returns the WindowManager.

2. Describe and create a window

import { DockMode } from '@realitycollective/iwsdk-uiextensions';

const status = host.createWindow({
id: 'status',
title: 'Player Status',
config: './ui/status.uikitml',
dockMode: DockMode.BodyFollow,
pinnable: true,
});

Every title-bar button, pin, dock, minimise and close, is off unless the window options ask for it.

3. Drive the window from code

status.onReady((panel) => panel.getElementById('uix-title'));
windows.togglePin('status');
windows.setChrome('status', { close: true });

Panels load asynchronously, so wait for onReady before reaching into a panel's elements. Getting started walks through this in detail, including what changes on the XR Blocks and three.js adapter.

Examples and runnable apps

AppWhat it showsLive
ShowcaseSix windows and two dock regions across the whole surface, on the IWSDK and desktop three.js pipelineswebxr-uiextensions.pages.dev
Multiplatform labThe same playground, picking IWSDK, XR Blocks or desktop three.js from the hardwarewebxr-uix-lab.pages.dev
Devtools playgroundThe showcase scene plus the edit gate and an in-headset UX Editornot deployed; run it locally
npm run dev:showcase # from the workspace root -> http://localhost:8081
npm run dev:multiplatform # the engine-picking demo, ?uix-engine= to force one
npm run dev:playground # the devtools playground, edit gate open in dev

What this stack is and is not

The Reality Collective WebXR packages aim at one outcome: an app's windows, docking and controls should not care which engine hosts them. The core, @realitycollective/webxr-uiextensions, ships engine-free, and the IWSDK and XR Blocks adapters are thin translations of the same decisions. When an app still has to reach into the host directly, either a contract is missing, which is a bug to report, or the app is overreaching.

Portable world-building is not a current promise. A SceneDescriptor lets this family's own windows and regions be authored as portable data, but scene content, meshes, prefabs and placement, is still built by the app. A shared content descriptor across families is only worth considering once a second host is actually targeted for that kind of portability.

Feedback

Questions and problems go to the issue tracker.

Documentation