Develop

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>

2. Register the asset

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:
  1. Fetches the .uikitml file as text.
  2. Parses it with @drawcall/uikitml.
  3. Resolves relative TTF font URLs from the UIKitML file URL.
  4. Installs the stylesheet and creates the UIKit component tree.
  5. 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');

5. Query and interact

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.
See HUD placement for in-XR placement choices.

Runtime TTF fonts

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.

Errors and caching

  • 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.