UIKitML (Authoring)
Updated: Sep 4, 2026
UIKitML is a strict HTML- and CSS-like language for spatial UI. IWSDK parses .uikitml source with @drawcall/uikitml and instantiates live @pmndrs/uikit components in the browser.
The default IWSDK configuration includes HTML-like elements, Horizon components, and Lucide icons.
<style>
.panel-root {
flex-direction: column;
gap: 8;
padding: 16;
background-color: #18181b;
}
.action:hover {
background-color: #3f3f46;
}
</style>
<Panel class="panel-root">
<h1>Settings</h1>
<button id="save-button" class="action">
<ButtonIcon><Settings /></ButtonIcon>
Save
</button>
</Panel>
Names are case-sensitive. Unsupported tags and properties are parser errors. Use kebab-case CSS properties in inline styles and <style> blocks.
Put the source under public/ui.
public/
└── ui/
└── settings.uikitml
Vite serves the file during development and copies it unchanged into the production output. Do not create a compiled JSON counterpart.
Register and instantiate the asset
const assets = {
settings: {
type: AssetType.UIKitML,
url: '/ui/settings.uikitml',
},
} satisfies AssetManifest;
const world = await World.create(container, {
assets,
features: { spatialUI: true },
});
const settings = await world.assets.instantiate<UIKitMLAsset>('settings');
const entity = world.createTransformEntity(settings);
The loader fetches, parses, and instantiates the source. It returns a UIKitMLAsset, which is a placeable Three.js Group containing its UIKitDocument.
Use stable IDs for controls referenced by application code.
const settings = world.requireSceneObject<UIKitMLAsset>('settings-panel');
const saveButton = settings.requireElementById('save-button');
saveButton.addEventListener('click', saveSettings);
Before handling these events, add RayInteractable to the panel’s owning
entity for browser-pointer and XR-ray input, PokeInteractable for touch input,
or both.
requireElementById() throws when a required element is missing. Use getElementById() when absence is valid.
Arbitrary data-* attributes are not UIKit component properties. Store associated application values in TypeScript or ECS data.
Declare each font weight with @font-face.
<style>
@font-face {
font-family: 'Brand Sans';
src: url('./fonts/BrandSans-Regular.ttf');
font-weight: 400;
}
@font-face {
font-family: 'Brand Sans';
src: url('./fonts/BrandSans-Bold.ttf') format('truetype');
font-weight: 700;
}
.title {
font-family: 'Brand Sans';
font-weight: 700;
}
</style>
<h1 class="title">Welcome</h1>
IWSDK resolves relative font URLs from the UIKitML document URL and loads the requested TTF weight at runtime.
Pass typed component sets through the spatial UI feature configuration.
const world = await World.create(container, {
features: {
spatialUI: {
kit: 'horizon',
componentSets: [applicationComponents],
},
},
});
The kit value is horizon by default; default selects the base set. Custom definitions participate in parser validation and runtime instantiation.
- Fetch failures identify the UIKitML URL and HTTP status.
- Parse failures include source location information.
- Unknown elements and unsupported properties fail validation.
- A failed asset rejects
instantiate() or the scene load that references it.