Game UI widgets
A widget is a piece of game UI you define once and use anywhere as a
tag: a stat bar, an item slot, a modal dialog. It has its own markup and CSS, props (typed inputs
with defaults) and slots (places the screen fills with its own content). Widgets work everywhere
markup does: screens, built-in screen templates and ui_html.
<div class="top-left stack" style="width: 280px;"> <stat-bar label="Health" icon="heart" value="{{hp}}" max="{{hp_max}}" tone="bad"/> <stat-bar label="Stamina" icon="lightning" value="{{stamina}}" tone="good" show_value="false"/></div><modal-dialog title="Paused" close="resume" exit="300ms"> <p>The world waits.</p> <icon-button slot="footer" label="Resume" variant="primary" onclick="resume"/></modal-dialog>Built-in widgets
Section titled “Built-in widgets”The engine ships eight widgets. A project widget with the same name replaces one.
| Widget | Props | Slots |
|---|---|---|
stat-bar |
label, value (100), max (100), icon, tone, show_value (true) |
— |
icon-button |
label, icon, onclick, variant (default, primary, ghost, danger), hotkey, disabled |
Default, after the label |
item-slot |
icon, image (a project texture), count (0 hides it), hotkey, rarity (common, uncommon, rare, epic, legendary), selected, onclick |
— |
modal-dialog |
title, subtitle, icon, close (the X button’s action; empty hides it), width (480 px) |
Default (the body), footer |
key-hint |
cap (the key’s text, default E), label, hold |
— |
tab-bar |
tabs (a list of {id, label, icon}), active (the current tab’s id), onclick (default tab) |
— |
notice-card |
icon (default info), title, text, tone |
Default |
progress-ring |
value (0), max (100), label, size (84 px), tone |
— |
tone is accent (the default), good, bad or info. Icons are Phosphor icon names. An
item-slot without onclick is not a button, so a HUD hotbar keeps the mouse captured. A tab-bar
click sends its onclick action with the clicked tab’s id as tab.
Use a widget
Section titled “Use a widget”- Props are attributes. Each value is converted to the type of the prop’s default: numbers
(
value="{{hp}}"), booleans (a bare attribute such asselectedis true,"false"is false), and lists or objects as JSON (tabs="{{json tabs}}"). A missing or empty attribute keeps the default. - Children fill slots. Plain children go to the default slot; a child with
slot="footer"goes to thefooterslot. - The root takes the tag’s
class,style,id,key,exitanddata-*attributes, plus the widget’s name as a class. So<stat-bar class="fx-slide-left" key="{{id}}"/>animates and keeps its element in a keyed list, and.stat-barstyles every instance. - Clicks. A widget’s buttons take their action from a prop, so the screen decides the event:
<icon-button onclick="buy" data-item="{{id}}"/>sendsui:buywithitem.
Unknown props, wrong types and values outside a prop’s options are reported as warnings, and the prop
keeps its default. The warnings appear in the editor, in ui.render and in ui_widget.render.
Make a widget
Section titled “Make a widget”- Click Settings in the toolbar and open Game UI widgets. The list shows built-in widgets and your own; customised built-ins are marked.
- Type a name and click New widget. Names are lowercase words joined by hyphens, such as
shop-item; the hyphen is required, so a widget never clashes with a built-in tag. - Fill in the Description, add props with + Prop (name, default, options, description), and write the HTML and CSS.
- Write an Example usage. The preview renders it and lists warnings and button events; turn on Show in viewport to see it with the real game renderer.
- Click Apply. Revert discards your edits.
To change a built-in, select it and click Customise: the project gets a copy you can edit, and Reset to built-in drops your changes. A widget of your own is removed with Delete, which is refused while a screen, template or widget still uses it.
{ "name": "shop-item", "description": "A row in a shop: icon, name, price and a buy button.", "props": { "name": {"default": "Item", "description": "Item name"}, "price": {"default": 0, "description": "Price in gold"}, "gold": {"default": 0, "description": "The player's gold"}, "icon": {"default": "package"}, "rarity": {"default": "common", "options": ["common", "rare", "epic"]}, "onclick": {"default": "buy"} }, "html": "<div class=\"card rarity-{{rarity}}\"><div class=\"spread\"><div class=\"inline\"><item-slot icon=\"{{icon}}\" rarity=\"{{rarity}}\"/><div class=\"stack\"><p>{{name}}</p><slot/></div></div><icon-button label=\"{{price}} g\" icon=\"coins\" variant=\"primary\" onclick=\"{{onclick}}\"{{#lt gold price}} disabled{{/lt}}/></div></div>", "css": ".shop-item { width: 380px; } .shop-item.rarity-epic { border-color: #b46bff; }", "example": "<shop-item name=\"Iron sword\" price=\"120\" gold=\"80\" rarity=\"rare\" icon=\"sword\"><p class=\"label\">+4 attack</p></shop-item>"}- The HTML renders to one root element. It reads props with the same bindings and helpers as
screens:
{{label}},{{#flag}}…{{/flag}},{{pct value max}},{{#eq a b}}…{{/eq}}(see Data bindings). <slot/>is replaced by the tag’s children and<slot name="footer"/>by the children withslot="footer". A slot’s own children are its fallback:<slot name="title">Untitled</slot>.{{#slots.footer}}…{{/slots.footer}}shows a part only when that slot is filled.- A prop’s type is the type of its default: text, number, boolean, list or object.
optionslimits a text prop to a set of values, and the default must be one of them. - Prop names are lowercase letters, digits and
_, and cannot beclass,style,id,key,exit,slotorslots. - Widgets can use other widgets, up to 12 levels deep. A widget that uses itself is an error.
- Scope the CSS with the widget’s name (
.shop-item …). Widget CSS loads after the UI kit and before the project stylesheet and the screens, so a screen can restyle a widget.
Limits
Section titled “Limits”| Limit | Value |
|---|---|
| Project widgets | 64 |
| Props per widget | 32 |
| HTML, CSS | 32 KiB each |
| Description | 2,000 characters |
| Example | 8 KiB |
| Name | 48 characters |
| Nesting | 12 levels |
| Elements one screen expands to | 20,000 |
Automate it
Section titled “Automate it”| Command | Purpose |
|---|---|
ui_widget.get |
Lists every widget, built-in and project, with its props (type, default, options, description), slots and example. With name, also its HTML, CSS, the expanded example and used_by |
ui_widget.upsert |
Creates or changes a project widget; fields you omit keep their value. A built-in’s name makes an override that starts from the built-in. rename_from renames a project widget and updates its tags in screens, widgets and templates |
ui_widget.remove |
Deletes a project widget; refused while something uses it, unless it overrides a built-in, which then comes back |
ui_widget.render |
Expands a widget (name with optional props and children, or name alone for its example) or any markup, without saving. Returns the markup, warnings and buttons |
Save the JSON above as shop-item.json, then create the widget and preview it with other props:
feather-cli --connect .feather-session.json command ui_widget.upsert --json - < shop-item.json
feather-cli --connect .feather-session.json command ui_widget.render \ --json '{"name":"shop-item","props":{"name":"Bow","price":50,"gold":80},"children":"<p>Ranged</p>"}'ui_widget.render lists one button with onclick buy. A --verify-run report lists screens whose
widgets did not expand under ui.widget_errors.
See Agents & automation for connecting a live session.