Author → Parse → Instantiate → Interact
Updated: Sep 4, 2026
IWSDK loads UIKitML source files directly. There is no generated JSON file or UIKitML-specific Vite plugin between authoring and runtime.
1. Author a public UIKitML asset
Place .uikitml files under public/ui so Vite serves them during development and copies them into the production build.
public/
└── ui/
├── menu.uikitml
└── fonts/
└── BrandSans-Regular.ttf
Use supported, case-sensitive elements. Use IDs for runtime queries and classes for reusable styles.
<style>
.menu {
flex-direction: column;
gap: 8;
padding: 16;
}
</style>
<Panel class="menu">
<h1>Settings</h1>
<button id="start-button">Start</button>
</Panel>
Declare the source file as an AssetType.UIKitML manifest entry.
import {
AssetManifest,
AssetType,
PokeInteractable,
RayInteractable,
UIKitMLAsset,
World,
} from '@iwsdk/core';
const assets = {
menu: {
type: AssetType.UIKitML,
url: '/ui/menu.uikitml',
},
} satisfies AssetManifest;
const world = await World.create(container, {
assets,
features: { spatialUI: true },
});
AssetType also includes GLTF, Audio, Texture, and HDRTexture.
3. Instantiate and place the panel
const menu = await world.assets.instantiate<UIKitMLAsset>('menu');
const menuEntity = world.createTransformEntity(menu);
menuEntity.object3D!.position.set(0, 1.4, -1.5);
menuEntity.object3D!.scale.setScalar(0.25);
menuEntity.addComponent(RayInteractable);
menuEntity.addComponent(PokeInteractable);
At instantiation, IWSDK:
- Fetches the
.uikitml file as text. - Parses it with
@drawcall/uikitml. - Resolves relative TTF font URLs from the UIKitML file URL.
- Installs the stylesheet and creates the UIKit component tree.
- Wraps the root in
UIKitDocument and returns a UIKitMLAsset.
UIKit dimensions are centimeters. The entity transform controls the placed panel’s world-space size.
4. Use a scene-authored asset
A native scene can instantiate the same manifest entry.
{
"id": "settings-panel",
"content": { "type": "asset", "asset": "menu" },
"transform": {
"position": [0, 1.4, -1.5],
"scale": 0.25
},
"components": {
"RayInteractable": {},
"PokeInteractable": {}
}
}
Find the placed asset by its scene node ID.
const menu = world.requireSceneObject<UIKitMLAsset>('settings-panel');
UIKitMLAsset forwards element lookup to its document.
const startButton = menu.requireElementById('start-button');
startButton.addEventListener('click', () => {
startExperience();
});
The panel’s entity must have RayInteractable for browser-pointer and XR-ray
input, PokeInteractable for touch input, or both. With the corresponding tag,
IWSDK’s pointer stack drives hover, active, focus, and pointer events for
browser and XR input.
6. Choose placement behavior
Use the entity’s authored transform for world-space UI. Add ScreenSpace for a browser viewport overlay:
menuEntity.addComponent(ScreenSpace, {
width: '360px',
height: '180px',
top: '24px',
right: '24px',
zOffset: 0.2,
});
Outside XR, ScreenSpace temporarily places the document under the camera. During XR, the document returns to its authored world transform.
Declare fonts with CSS @font-face inside the UIKitML source.
<style>
@font-face {
font-family: 'Brand Sans';
src: url('./fonts/BrandSans-Regular.ttf');
font-weight: 400;
}
.title {
font-family: 'Brand Sans';
font-weight: 400;
}
</style>
<h1 class="title">Launch ready</h1>
The relative URL above resolves to public/ui/fonts/BrandSans-Regular.ttf.
- A fetch error identifies the UIKitML URL and HTTP status.
- A parser error identifies its source line and column.
- A failed manifest asset rejects
instantiate() or scene loading. - UIKitML and font files use normal static-asset cache and versioning rules.
PanelUI and PanelDocument remain available for raw-URL compatibility. Use manifest assets for new panels.