Skip to content

Common props

Every element accepts the props on this page, in addition to its own. Each element’s page lists only what is specific to it. A component you write takes these too, undeclared: a prop it does not name lands on its root element — the fallthrough rule — with one exception, the handlers, which never fall through.

  • x: A number of pixels from the parent’s left edge; one of "left", "center", "right"; a percentage like "25%"; or an anchor with an offset — { left: n }, { center: n }, { right: n }, where n is a pixel count or a percentage of the parent ({ right: '15%' }).
  • y: The same vertically: "top", "center", "bottom", a number, a percentage, or { top: n }, { center: n }, { bottom: n }.
  • w: A number of pixels; "fill" for the parent’s width; "aspect" to keep the natural proportions given h; a percentage; or { fill: n } for the parent’s width less n pixels.
  • h: The same vertically.
  • right: A number. Inset from the parent’s right edge — the same as x={{ right: n }}.
  • bottom: A number. Inset from the parent’s bottom edge.
src/chrome.ts2
<sprite x="left" y="top" w={CORNER} h={CORNER} sprite={topLeft} tiling />
<sprite x="right" y={CORNER} w={CORNER} h={{ fill: CORNER * 2 }} sprite={right} tiling />
<sprite x={{ right: 3 }} y={6} w={26} h={23} sprite={Chrome.CLOSE} tiling />

Layout and geometry has the vocabulary.

  • A size takes a pixel inset or a proportion, never both. { fill: '10%' } is refused (TS2E210). Write "90%", or { fill: n }.
  • "aspect" needs the other axis to be something.
  • A ? : may mix forms: x={i % 2 === 0 ? 0 : 'right'}, w={wide === 1 ? { fill: 8 } : 40}.
  • hide: A boolean. If true the component is not drawn and takes no input. Nothing under a hidden component updates: no timer ticks, no watch fires, until it is shown again. The value may be a comparison — hide={n === 0}.
  • ref: A Ref. Binds this component to the ref so a handler can change or measure it. A ref is bound to exactly one component.
  • options: An array, in menu order. Each entry is a string, or an object { label, hotkey, modifiers? } that puts a key on the option. An empty entry leaves that slot open: options={['', '', 'Bank-all']} puts one option third. The list may be written inline, named, or chosen at run time between two named lists. A label may be a run-time string.
  • optionSubject: A string. The noun after the verb — “Wield Rune scimitar”.
  • optionPriority: A number. Whose options come first when two components overlap under the pointer.
  • continueOp: A literal true. Offers a dialogue’s “Continue” entry; a click on it is what the server hears.
  • targetVerb: A string. The verb used when this component is the target of a spell or an item.
src/inventory.ts2
<sprite x="center" y="center" w={36} h={32} item={item} itemCount={count}
options={['Use', '', '', '', 'Drop', '', '', '', '', 'Examine']} />

Use is option 1, Drop is 5 and Examine is 10 — the positions the game puts them in.

src/menu.ts2
<text x={0} y={84} w="fill" h={16} font={Fonts.PLAIN}
text={dismissed === 1 ? 'Dismissed' : 'Click here to continue'}
align="center" valign="middle" shadow color={0x0000ff} continueOp
options={[{ label: '', hotkey: KEY_ESCAPE }]}
onOptionSelect={() => { setDismissed(1); }} />

An empty label with a hotkey is a keyboard-only option: the key fires it, and the menu shows nothing for it. hotkey is the client’s own key code — Escape is 13 — and modifiers asks for a held modifier: 1 ctrl, 2 alt, 4 shift. Hover and menus has the whole menu.

  • onOptionSelect with no options is unreachable. The compiler does not refuse it; write both.
  • An options list with no onOptionSelect is allowed; the server may answer it.
  • A left click chooses the first option with a label, the top of the menu; if that option is sixth or later, a left click opens the menu instead.

The first four take a literal true or false (TS2E221).

  • continueOp: Above.
  • dragTarget: A dragged component may be dropped onto this one.
  • spellTarget: A selected spell may be cast on this component.
  • allowRunScript: This component may ask the server to run something.
  • spellTargets: An array of "groundItem", "npc", "object", "player", "component". What a spell or item on this component may be used on. Pair it with targetVerb.
  • interactionDepth: A number from 0 to 7. How many parents up the component that actually reacts to a click or drag on this one is; 0 is this component.
  • noClickThrough: A boolean. Clicks inside this component do not reach whatever is behind it.
  • noScrollThrough: A boolean. Wheel events inside this component do not reach whatever is behind it.
refused/common-props.ts2
<text w="fill" h={16} text="Drop here" font={Fonts.PLAIN} color={Colors.AMBER}
dragTarget={armed === 1} // must be `dragTarget` or nothing
onClick={() => { setArmed(1 - armed); }} />
  • draggable: A Ref to the container the drag is measured in. The component cannot leave that box, and every position a drag reports is relative to it.
  • dragMoves: A boolean. The component stays where its props put it and nothing is carried under the pointer — a thumb. Without it the client carries a copy of the component and puts it back on release — an item.
  • dragDeadZone: A number of pixels the pointer moves before a press becomes a drag.
  • dragDeadTime: A number of client cycles the button is held before a press becomes a drag.
src/slider.ts2
<sprite x={level * TRAVEL / 100} y={0} w={PIECE} h={PIECE} sprite={Slider.THUMB}
draggable={zone} dragMoves dragDeadZone={0} dragDeadTime={0}
onDrag={(event) => { setLevel(Math.clamp(0, 100, event.mouseX * 100 / TRAVEL)); }} />

Drag and drop has the rest.

  • hover: An object of prop overrides applied while the pointer is inside the component and undone when it leaves. Any prop of the element may appear in it — sprite, color, hide, transparency — plus one of its own:
    • releaseAfterMs: A number. Hold the override this long after the pointer leaves, including leaving the window.
src/settings.ts2
<sprite x={4} y="center" w={16} h={16}
sprite={on === 1 ? Icons.CHECKBOX_ON : Icons.CHECKBOX_OFF}
hover={{ sprite: Icons.CHECKBOX_HOVER }} />

The value it returns to is whatever the prop says — here a conditional, so a ticked checkbox goes back to ticked.

src/menu.ts2
<rect w="fill" h="fill" color={0x2a2218} fill
hover={{ color: 0x5a4a2a, releaseAfterMs: 400 }} />
  • hover cannot be combined with onMouseEnter or onMouseLeave on the same component (TS2E217).
  • releaseAfterMs cannot sit beside onTimer on the same component (TS2E217).
  • static: A bare flag, or a string name. Makes this component addressable: a box another interface opens into, a component the server can name, a component a handler elsewhere can write to. On a <layer> its children are addressable too. Name it with a module-scope createRef bound to its ref when something outside the build addresses it.
src/integrating.ts2
<layer static ref={walletSlot} x={12} y={44} w={88} h={126}>
<Wallet />
</layer>
<layer static ref={offerSlot} x={112} y={44} w={128} h={126} />
  • Nothing above it may read state. static under a component that reads state, a conditional, or a handler that captures a local is refused (TS2E224).
  • Directly inside <ui>, only a <layer> may be static (TS2E302).
  • contentType: One of "fpsCounter", "worldView", "minimap", "compass", "worldMap", "worldMapOverview", "loginRunes", or a raw number for a surface the client does not name. Marks the component as one the client draws into itself — the 3D scene, the minimap — rather than something you draw.
  • rebuildOn: A whole number. Remake every conditional region among this element’s children whenever the value moves, whether or not a condition changed its answer. For “the server rebuilt this list”: a counter the rows are made afresh from.
  • It goes on the element that holds the region. With no {cond && …} among the children it gates nothing and is refused (TS2E256); on a component call it is refused too (TS2E259).
  • The key is a whole number. A string key is refused by name (TS2E258); keep the list in its string cell and give the region a companion Int cell whose only job is to be the key.
  • A rebuild deletes: refs, focus and hover state inside the region go with it. It does not reach into a nested component’s own regions.
  • deferred: A boolean. Leave the interface definition’s own value standing for this element’s state-reading props until the state first moves, rather than writing the current value as the interface opens. For a component whose resting look is the definition’s own until the server first changes it. It is ignored on a prop that reads no state. Not on <ui>.

A handler is an arrow function written on the prop, or an arrow that calls a function you named. What the player did arrives as its parameter — see Event — and the parameter is optional.

PropFires when
onClickthe player clicks
onClickRepeatrepeatedly, while the button is held down
onHoldwhile the pointer is held on the component
onReleasethe button is released
onMouseEnterthe pointer enters
onMouseLeavethe pointer leaves
onMouseRepeatevery cycle, while the pointer is inside
onScrollWheelthe wheel turns over the component
onOptionSelecta right-click option is chosen; event.option says which
onDragthe component is dragged; event.mouseX/mouseY are inside the draggable container
onDragCompletethe drag ends; event.dragTarget is what it landed on, or -1
onTargetEnter, onTargetLeavea spell or item on this component is selected as something to use, and unselected — not a drag
onKeya key is pressed while the interface is open; event.keyCode, event.keyChar
onResizeresizing the game window, or the server moving the interface to another slot, changed the component’s size; a ref write does not
onTimerevery client cycle, for as long as the timer is set — stopTimer clears it
src/shop.ts2
<layer x={i * 79} y={0} w={74} h={22} onClick={() => { setTab(i); }}>
  • A handler does not fall through to a component. <Row onClick={…} /> on a component that declares no onClick is refused (TS2E202), even though its root would take one; and a function cannot be a declared prop either. Put the handler on an element the component draws, or on a <layer> around the call.
  • Write the arrow: onClick={() => { restoreDefaults(); }}, not onClick={restoreDefaults}.
  • null takes a handler off; absent writes nothing. null on one arm of a conditional, or assigned through a ref, clears a bound handler. An unconditional onClick={null} on an element is refused (TS2E206).
  • onTimer is the client’s own timer and fires every cycle. Prefer useInterval or useDelay unless the step is the cycle.
  • onVarTransmit, onInventoryTransmit and onSkillTransmit are refused (TS2E218): use useWatch. Nothing binds onChatTransmit; watch Engine.CHAT.
  • onLoad: A handler run once, as the interface opens. Its argument is a call to a function of yours whose arguments are constants or event.self.
src/watch.ts2
<text ref={poisonLabel} x="right" y="center" w={120} h={16} text="Poison: -"
font={Fonts.PLAIN} align="right" valign="middle" shadow color={Colors.WHITE}
onLoad={(event) => { writePoison(event.self); }} />
  • Not on an element under a component that reads state, a conditional, or a handler that captures a local. There it is refused (TS2E206).
  • It cannot be null.

My component is in the corner instead of where I positioned it

Section titled “My component is in the corner instead of where I positioned it”

x and y are measured from the parent’s top-left corner. If the parent is larger than you think — a <layer w="fill" h="fill"> fills the whole interface — the offsets land somewhere unexpected. Give the parent a size, or anchor the child with x="center" instead of computing a position.

My panel scrolls the game world as well as itself

Section titled “My panel scrolls the game world as well as itself”

Add noScrollThrough to the panel.

Check that options is set, not only onOptionSelect. If the option is the server’s to answer, options alone is correct and there should be no onOptionSelect.

Nothing under my hidden layer updates when I show it again

Section titled “Nothing under my hidden layer updates when I show it again”

If the container carries a read or a timer that must keep running, put the hide on the children instead.

Something between the slot and the interface is built at run time. The message names the part — usually a component that reads state, a conditional, or a handler that captures a local. Move the slot above that part, or take the read out of it.