UIKitML (Authoring Language)
UIKitML lets you author spatial UI with familiar HTML/CSS‑like syntax. The toolchain provides:
parse(text)
→ JSON suitable for transportinterpret(parseResult)
→ live UIKit components in the scenegenerate(...)
→ optional HTML/Style output for tools/round‑trip
Language Highlights
- Elements map to UIKit components:
<container>
,<text>
,<image>
,<video>
,<svg>
, and<input>
.
- Classes and IDs:
class="foo bar"
,id="menu"
; selectors are available at runtime viaUIKitDocument
.
- Conditional styles:
- Pseudo‑like keys:
hover
,active
,focus
, and responsive groups:sm
,md
,lg
,xl
,2xl
.
- Pseudo‑like keys:
- Data attributes:
data-*
are preserved onto the component’suserData
(e.g.,data-foo
→userData.foo
).
- Custom elements:
- Unknown tags become
custom
and can be mapped to actual components by providing a “kit” (constructor map) tointerpret
.
- Unknown tags become
Parsing and JSON
parse(text, { onError })
returns an object like:
ts
{
element: /* ElementJson or string */, // the root element tree
classes: { [className]: { origin?: string, content: Record<string, any> } },
ranges: { [uid]: { start: { line, column }, end: { line, column } } }
}
This JSON is compact and safe to ship as a static file. IWSDK’s Vite plugin writes it to public/ui/*.json
.
Interpreting at Runtime
interpret(parseResult, kit?)
produces a UIKit component tree. IWSDK wraps this in a UIKitDocument
and attaches it to your entity.
ts
import { interpret } from '@pmndrs/uikitml';
const rootComponent = interpret(parseResult); // -> UIKit component
Example
html
<container id="menu" class="panel" style="padding: 12; gap: 8">
<text class="title" style="fontSize: 24">Settings</text>
<container class="row" style="flexDirection: row; gap: 6">
<text>Music</text>
<input id="music" />
</container>
</container>
With a class block:
css
.panel {
backgroundcolor: rgba(0, 0, 0, 0.6);
sm: {
padding: 8;
}
}
.title {
hover: {
color: #fff;
}
}
See also: Flow, UIKitDocument
Authoring Details
- Inline
style
vs class blocks:- Inline
style
is merged with class styles; conditionals understyle
(e.g.,hover
,sm
) are supported and serialized separately.
- Inline
<style>
blocks in UIKitML:- The parser extracts
.class
and#id
rules from<style>
tags and merges them intoclasses
withorigin
metadata.
- The parser extracts
- Property names are camelCase (e.g.,
backgroundColor
,fontSize
) to align with JavaScript style objects. - Strings vs numbers:
- Numeric values are in UIKit units (cm). Colors accept CSS‑like strings (e.g.,
#fff
,rgba(...)
).
- Numeric values are in UIKit units (cm). Colors accept CSS‑like strings (e.g.,
Conditional Precedence
- Base styles apply first, then conditional groups are layered at runtime:
- Order of application: base → responsive group (
sm..2xl
) → interactive (hover
,focus
,active
).
- Order of application: base → responsive group (
- Use class composition to avoid deep inline conditionals when multiple states combine.
Custom Components with a Kit
You can map unknown tags to custom UIKit components using a kit:
ts
import { interpret } from '@pmndrs/uikitml';
import { Component } from '@pmndrs/uikit';
class Gauge extends Component {
/* ... */
}
const kit = { gauge: Gauge }; // tag <gauge> maps to Gauge
const root = interpret(parseResult, kit);
Unknown tags without a kit entry fall back to Container
and store userData.customElement
for inspection.
Debugging & Tooling
ranges
link elements to source lines/columns (useful for editor overlays and inspector panels).- The parser injects
data-uid
attributes for stable identification during authoring; the generator strips them for clean output if you round‑trip. - The Vite plugin will log parse errors with filenames and minimal context; turn on
verbose: true
for more detail.
Best Practices
- Prefer classes for reusable styling; use IDs for unique elements you’ll query at runtime.
- Keep media references (
src
) relative to your public assets; the interpreter preserves them into UIKit properties. - Avoid overly deep nesting; flat, flex‑oriented hierarchies layout faster and are easier to animate.