Skip to content

Writing markup

Markup is written directly inside your functions, with seven built-in elements.

ElementDraws
<layer>nothing. A container, and the only element that can hold others
<rect>a filled or outlined rectangle
<text>a line or block of text
<sprite>an image, or an item icon
<model>a 3D model, an item, or an NPC’s head
<line>a straight line
<input>an editable text field

A button, a checkbox or a scrollbar is built from these. <ui> declares the interface itself; see <ui>.

A tag is a value of type Component. You can return it, assign it, and pass it to a component:

const icon = <sprite w={18} h={18} sprite={Icons.COINS} />;

null is the markup that draws nothing. A component may return it:

src/wares.ts2
const ware = WARES.at(i);
if (ware === null) {
return null;
}

Two siblings cannot be returned side by side. Wrap them in a <layer> or a fragment, <>…</>:

return (
<>
<text y={0} text="Name" w={80} h={16} font={Fonts.PLAIN} />
<text y={16} text="Price" w={40} h={16} font={Fonts.PLAIN} />
</>
);

A <layer> has a position and a size, clips its children, and hides or moves as a unit. A fragment’s children are placed straight into its parent, so a component that returns one cannot be placed with x or y; see Passing props.

<rect />, <sprite />, <text />. An element with children uses the long form:

<layer w="fill" h="fill">
<sprite w="fill" h="fill" sprite={Chrome.BACKDROP} tiling />
</layer>

Anything in {…} is an expression. A template literal interpolates, and ? : picks between two values:

src/wares.ts2
<text x="right" y={0} w={66} h={16} text={`${ware.price} gp`}
font={Fonts.PLAIN} align="right" valign="middle" shadow
color={ware.price <= coins ? Colors.AMBER : Colors.RED} />

Quoted attributes are strings, and a bare attribute is true: fill means fill={true}. stringOf(n) turns a number into text when there is nothing to put around it:

src/quest-log.ts2
<text x="right" y="center" w={40} h={16} text={stringOf(value)} font={Fonts.PLAIN}
align="right" valign="middle" shadow color={color} />

A <sprite> or <text> cannot contain anything. Put the pieces side by side in a layer:

src/wares.ts2
<layer x={6} y={y} w={{ fill: 12 }} h={rowHeight(ware)}>
<sprite x={0} y={0} w={36} h={32} item={ware.item} itemCount={-1} />
<text x={44} y={0} w={116} h={14} text={ware.name} font={Fonts.PLAIN}
valign="middle" shadow color={Colors.WHITE} />
<text x={44} y={13} w={116} h={12} text={categoryLabel(CategoryNames, ware)}
font={Fonts.SMALL} valign="middle" shadow color={Colors.DIM} />
</layer>

Later siblings draw on top of earlier ones, a static layer aside.

Comments inside markup go in braces:

<layer w="fill" h="fill">
{/* The backdrop has to come first — children draw in order */}
<sprite w="fill" h="fill" sprite={Chrome.BACKDROP} tiling />
</layer>

Appearance is set with props (color, font, sprite, transparency), and text with the text prop:

<text text="Confirm" w="fill" h={16} font={Fonts.BOLD} /> // yes
<text w="fill" h={16} font={Fonts.BOLD}>Confirm</text> // no