End‑to‑End Flow
This is how spatial UI moves from source files to live, interactive 3D panels.
1) Author in UIKitML
- Create files under
ui/*.uikitml
using HTML/CSS‑like syntax. - Use IDs/classes for runtime querying, and conditional style blocks (
hover
,active
,focus
,sm..2xl
).
2) Compile to JSON (Vite Plugin)
@iwsdk/vite-plugin-uikitml
watches your source directory (defaultui
) and writes JSON topublic/ui
in dev.- On production builds, it compiles once at
buildStart
and prints a summary. - Output is the raw result of
parse(text)
from@pmndrs/uikitml
.
Minimal plugin config (defaults shown):
ts
// vite.config.ts
import UIKitML from '@iwsdk/vite-plugin-uikitml';
export default {
plugins: [
UIKitML({
sourceDir: 'ui', // where .uikitml lives
outputDir: 'public/ui', // where .json is written
watch: true,
verbose: false,
include: /\.uikitml$/, // files to compile
}),
],
};
3) Ship JSON
- The JSON files are static assets (ideal for CDNs and caching).
- Each UI panel typically corresponds to a single JSON file, but you can compose within UIKitML as you prefer.
4) Interpret at Runtime (SDK)
- In code, point a
PanelUI
component at the JSON path and add it to an entity.
ts
entity.addComponent(PanelUI, {
config: '/ui/menu.json', // served from public/ui
maxWidth: 1.0, // meters (world‑space cap)
maxHeight: 0.6,
});
PanelUISystem
will:fetch(config)
→ load JSONinterpret(parseResult)
→ build a UIKit root component- wrap it in a
UIKitDocument
and attach to your entity’sobject3D
- set stable transparent sorting for readability
- forward pointer events (configurable) so hover/click/drag work via XR rays and hands
- call the UIKit root’s
update()
each frame (animation/tween support)
5) Size and Placement
- World‑space: system applies target dimensions based on
PanelUI.maxWidth/maxHeight
and the entity’s world scale. - Screen‑space: add
ScreenSpace
to place the panel relative to the camera using CSS‑like strings:
ts
entity.addComponent(ScreenSpace, {
width: '40vw',
height: 'auto',
bottom: '24px',
right: '24px',
zOffset: 0.25,
});
When XR starts, panels automatically return to world space; when XR stops, they return to screen space.
6) Interact and Query
- Use
UIKitDocument
fromPanelDocument
to reach into the UI tree:
ts
const doc = entity.getValue(PanelDocument, 'document');
const startBtn = doc?.getElementById('start');
- Pointer events (via IWSDK’s input stack) drive hover/active/focus conditionals in your UI, and you can wire custom behaviors to element interactions.
See also: UIKit, UIKitML, UIKitDocument
Dev and Build Behavior (Detailed)
Dev server:
- On
vite serve
, the plugin compiles allui/*.uikitml
topublic/ui/*.json
and starts a watcher. - Edits to
.uikitml
trigger regeneration; your app canfetch('/ui/file.json')
without manual steps. - Enable
verbose: true
for detailed logs and parse diagnostics.
- On
Production build:
- On
vite build
, compilation runs once at the start; a summary lists generated files and any failures. - JSON assets are emitted to your
public/ui
path for static serving.
- On
File mapping:
ui/menu.uikitml
→public/ui/menu.json
(same relative structure under subfolders).
Caching & Delivery
- JSON is static and cacheable; you can version files by path if needed.
- Prefetch JSON for critical panels to avoid hitches on first open.
Error Handling
- If
fetch(config)
fails,PanelUISystem
throws an error with status details. - Parse errors during compilation are printed by the plugin; fix authoring issues and let the watcher regenerate.
Screen‑space Math (How it Works)
- The system evaluates CSS size/position using hidden DOM nodes to compute pixel dimensions.
- Pixels are mapped to meters at a camera‑relative plane
zOffset
meters in front of the near plane:worldHeightAtZ = 2 * tan(fov/2) * zOffset
worldPerPixel = worldHeightAtZ / canvasHeight
targetWorldWidth = pixelWidth * worldPerPixel
targetWorldHeight = pixelHeight * worldPerPixel
- The
UIKitDocument
then scales uniformly to fit those target meters.
Input Integration
- IWSDK forwards pointer events from controllers/hands to the 3D UI.
- UI state (
hover
,active
,focus
) is reflected in styles automatically. - Combine with your ECS input systems for gameplay logic.
Advanced Patterns
- Multiple panels:
- Attach several
PanelUI
documents to different entities; each can be world‑ or screen‑space independently.
- Attach several
- Dynamic themes:
- Toggle classes on root elements to switch palettes or layouts without rebuilding.
- Custom components:
- Provide a “kit” to
interpret(parseResult, kit)
to map custom tags to specialized UIKit components.
- Provide a “kit” to