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​