# Game UI

> Build HUDs, menus and shops with HTML-like markup, data bindings and CSS, and restyle the engine's built-in screens.

Source: https://feather-engine.com/docs/presentation/game-ui/

Game UI is written in an HTML-like markup and styled with CSS. The same renderer draws your own
screens, the engine's built-in HUD, dialogue box, journal and prompts, so one stylesheet restyles the
whole game. UI appears in the editor's Play mode and in exported games.

There are three ways to put UI on screen:

| Approach | Use it for |
| --- | --- |
| **Screens** | Project-stored HUDs, menus, shops and inventories with data bindings, shown from scripts |
| **Script elements** | Quick labels, bars, panels and toasts drawn directly from a script |
| **Templates** | Overriding the markup of the engine's built-in screens |

[Widgets](/docs/presentation/game-ui-widgets/) are reusable components, such as a stat bar or a modal
dialog, that you use as tags in any of the three.

## Create a screen

1. Click **Settings** in the toolbar and open **Game UI screens**.
2. Type a name and click **New screen**. Names use lowercase letters, digits, `-` and `_`.
3. Write the **HTML** and **CSS**, and describe the screen and the fields it reads in **Description**.
4. Enter **Sample data** as JSON. The preview renders the screen with it.
5. Set the **Layer**, from −100 to 100. Higher layers draw on top.
6. Click **Show in viewport** to see the screen over the game view.

Show the screen from a world script, then update only the data that changes:

```rust
fn start() {
    ui_show("hud", #{ health: 100, ammo: 30, items: [] });
}

fn on_player_hurt(amount) {
    ui_set("hud", "health", player_health());
}
```

The engine updates the existing widgets in place, so CSS transitions keep running as values change.

## Markup

| Category | Supported |
| --- | --- |
| Block tags | `panel`, `div`, `section`, `row`, `col`, `h1`–`h3`, `p`, `button`, `bar`, `hr`, `spacer`, `img`, `ul`/`ol`/`li`, `table`/`tr`/`th`/`td`. Unknown tags act as `div` |
| Inline tags | `b`, `strong`, `em`, `i`, `span`, `small`, `icon`, `a`, `br`, `code` |
| Common attributes | `class`, `id`, `style`, `title` (tooltip), `key` (see [Lists that animate](#lists-that-animate)) |
| Element attributes | `button id disabled onclick`; `bar value max label`; `img src width height` (a project texture name); `icon name`; `table striped` |
| Screen attributes | `exit` on the screen's first element: how long the screen stays to animate out, such as `exit="300ms"` |

Inline tags next to each other become one line of text and have no box of their own, so padding,
backgrounds and layout do not apply to them. Use a block tag, such as `<div class="badge">` instead of
`<span class="badge">`, whenever you need a box.

`<icon name="…">` takes any [Phosphor](https://phosphoricons.com) icon name, such as `thumbs-up` or
`rocket-launch`, plus game aliases such as `gold`, `health`, `quit` and `music`. Unknown names draw a
circle.

The screen's root element has the screen name as its class, so CSS can scope rules with `.hud …`.

### Data bindings

Screens use a Mustache subset:

| Syntax | Effect |
| --- | --- |
| `{{name}}` | Inserts a value, escaped for markup |
| `{{{name}}}` | Inserts a value unescaped |
| `{{#list}}…{{/list}}` | Repeats for each item of a list, or shows once when the value is true, a non-empty string or an object |
| `{{^list}}…{{/list}}` | Shows only when the value is missing, false or empty |
| `{{a.b}}` | Reads a field of an object |
| `{{.}}` | The current item |

Helpers compute values and compare them:

| Helper | Gives |
| --- | --- |
| `{{pct value max}}` | `value / max` as a whole percentage, 0 to 100, for widths and gauges; `max` defaults to 100 |
| `{{angle value max from to}}` | The degrees (0–360) that `value / max` fills of the `from`%–`to`% part of a round gauge, for `rotate` |
| `{{round x}}` | A whole number |
| `{{json x}}` | The value as JSON, to pass a list or object to a widget prop |
| `{{#eq a b}}…{{/eq}}` | Shows its content when `a` equals `b`; `{{^eq a b}}` shows it when they differ |
| `{{#lt a b}}…{{/lt}}`, `{{#gt a b}}…{{/gt}}` | Numeric less-than and greater-than |

Operands are field names, numbers, `true` or `false`, quoted text (`'gold'`) or a ratio of two
operands (`hp/max`): `{{#lt hp/max 0.25}}<p>Low health</p>{{/lt}}`.

```html
<panel class="shop">
  <h2>{{title}}</h2>
  {{#items}}
    <row>
      <span>{{name}}</span>
      <button onclick="buy" data-item="{{id}}">Buy</button>
    </row>
  {{/items}}
  {{^items}}<p>Sold out</p>{{/items}}
</panel>
```

### Lists that animate

Give repeated items a `key` and each one keeps its element, and any animation it is playing, when
the list changes:

```html
<div class="stack">
  {{#quests}}<div class="notice fx-slide-left" key="{{id}}"><p>{{title}}</p></div>{{/quests}}
</div>
```

With keys, a new item animates in on its own and removing one leaves the others alone. Without keys,
any change in the number of items rebuilds the list and every item restarts its animation.

### Button events

A button with `onclick="buy"` sends the event `ui:buy` to every script, with a map holding `screen` and
each `data-*` attribute. It also calls the `on_ui(screen, button)` hook.

```rust
fn start() {
    on("ui:buy", |e| {
        give_item(e.item, 1);
    });
}
```

## CSS

Each screen's CSS joins the project stylesheet. The renderer supports flexbox and grid layout,
gradients, `transition`, `@keyframes`, CSS variables, `:hover`, `:active`, `:nth-child`, `:has()`,
attribute selectors and `@media`. Tags are type selectors (`panel`, `button`, `bar`), `class` becomes
`.class`, `id` becomes `#id`, and other attributes become `[attr]` selectors.

`opacity` fades an element and everything inside it. `translate`, `scale`, `rotate` and `transform`
move elements, and `transition`, `@keyframes` and `animation` animate them.

The engine theme defines variables at `:root`: `--panel`, `--panel-strong`, `--line`, `--line-strong`,
`--text`, `--muted`, `--accent`, `--accent-bright`, `--good`, `--bad` and `--radius`. Override them to
re-theme every screen at once.

The game stylesheet is built in this order: the engine theme, the [UI kit](#the-ui-kit), widget CSS, the
project stylesheet, then each screen's CSS. Later rules win, so a screen can restyle anything before it.
A declaration the renderer does not support is dropped and the rest of the stylesheet still loads;
the problem is reported (in the editor, and as `ui.css_error` in a `--verify-run` report). When two
`@keyframes` share a name, the later one is used.

### The project stylesheet and fonts

Edit the stylesheet in **Settings → Game UI stylesheet**, or in **Window → Game logic → UI stylesheet**.
**Start from the built-in theme** copies the default rules so you can edit them. Click **Apply** to keep
your changes.

Click **Import font…** to add a TTF or OTF font, then use it with `font-family: "Name"`.

## The UI kit

The engine ships ready-made classes you can use in any screen, widget or `ui_html` markup.

### Motion

| Classes | Effect |
| --- | --- |
| `fx-fade-in`, `fx-slide-up`, `fx-slide-down`, `fx-slide-left`, `fx-slide-right`, `fx-pop`, `fx-zoom-in`, `fx-bounce`, `fx-wobble`, `fx-shake`, `fx-grow-x` | Play once when the element appears |
| `fx-pulse`, `fx-float`, `fx-spin`, `fx-blink`, `fx-glow` | Loop while the element is shown |
| `fx-slow`, `fx-fast` | Change the animation length to 0.9 s or 0.2 s |
| `delay-1` … `delay-6` | Delay the animation or transition in steps of 0.06 s |
| `stagger` | On a parent: its children animate one after another |

### Enter and exit

Give elements `enter-fade`, `enter-up`, `enter-down`, `enter-left`, `enter-right`, `enter-pop` or
`enter-zoom` and they animate in when the screen is shown. They also animate out when it is hidden, if
the screen's first element has an `exit` time: the screen then stays on screen that long, up to 10 s,
with the class `leaving`, and its buttons stop responding. On its first frame a screen has the class
`entering`. You can write your own rules for `screen.entering` and `screen.leaving`.

```html
<div class="scrim enter-fade" exit="350ms">
  <div class="card glass enter-pop" style="width: 480px;">
    <div class="card-header"><h2 class="title">Paused</h2><div class="badge">Day {{day}}</div></div>
    <div class="menu stagger">
      <button class="primary fx-slide-left" onclick="resume">Resume</button>
      <button class="fx-slide-left" onclick="settings">Settings</button>
    </div>
  </div>
</div>
```

### Screen effects

`ui_flash(screen, effect)` plays a one-off reaction on the whole screen: `shake`, `bounce`, `pulse`,
`wobble`, `glow` or `flash`. `ui_class(screen, "dim", true)` keeps a screen dimmed.

### Layout and components

| Classes | What they do |
| --- | --- |
| `center`, `top-left`, `top-right`, `top-center`, `bottom-left`, `bottom-right`, `bottom-center`, `fill` | Place an element on screen |
| `stack`, `inline`, `spread`, `grid-2`, `grid-3`, `grid-4`, `grow` | Arrange children in a column, a row, a spaced row, a grid, or fill the space |
| `scrim` | Dims everything behind a modal |
| `card`, `card-header` | A content card and its header row |
| `glass`, `neon` | A frosted panel look, and a glowing border |
| `title`, `subtitle`, `big-number`, `label` | Text styles |
| `badge` (with `good` or `bad`), `chip`, `divider` | A pill label, a small rounded tag and a thin line |
| `tabs` | A row of buttons; give the current one `active` |
| `toggle`, `toggle on` | A switch: `<div class="toggle on"><div class="knob"></div></div>` |
| `meter`, `meter-fill` | A bar whose fill you size, such as `style="width: {{pct hp max}}%"` |
| `bar.thin`, `bar.glow` | Bar variants |
| `menu` | A column of big buttons that slide when hovered or focused |
| `slot`, `slot.selected` | Inventory and hotbar cells |
| `tooltip`, `notice` | A small floating label, and a raised message box |

## Script API

### Screens

| Function | Description |
| --- | --- |
| `ui_show(name)`, `ui_show(name, data)` | Shows a screen, optionally with data |
| `ui_set(name, key, value)` | Changes one data field |
| `ui_data(name)` | Returns the screen's current data |
| `ui_hide(name)` | Hides a screen |
| `ui_toggle(name)` | Shows or hides a screen; returns whether it is now shown |
| `ui_shown(name)` | Whether a screen is shown |
| `ui_attach(name, target)`, `ui_attach(name, target, height)` | Makes a shown screen follow an object, `"player"` or a `vec3`, for health bars, prompts and markers |
| `ui_surface(name, object)`, `ui_surface(name, object, width, height)` | Draws a shown screen or a `ui_html` id onto an object instead of over the game view |
| `ui_detach(name)` | Returns an attached screen to the layout, or takes it off its object |
| `ui_class(name, class, on)` | Adds or removes a class on the screen, for CSS such as `screen.boss …` |
| `ui_flash(name, class)`, `ui_flash(name, class, seconds)` | Adds a class for a while (default 0.6 s, up to 30 s); flashing again restarts it |
| `ui_var(name, var, value)` | Sets the CSS variable `--var` on the screen; numbers stay unitless, so pass `"40%"` or `"12px"` for units |

One screen can be shown many times as instances by adding `#` and a suffix, such as
`ui_show("enemy_bar#12", data)`.

`ui_class`, `ui_flash` and `ui_var` also work on `ui_html` ids. Class names are 1 to 48 letters,
digits, `-` or `_`; `entering` and `leaving` are reserved for the engine. A variable value is at most
200 characters, without `;`, `{` or `}`.

```rust
fn on_player_hurt(amount) {
    ui_set("hud", "health", player_health());
    ui_var("hud", "hp", player_health() + "%");   // width: var(--hp) in the screen's CSS
    ui_flash("hud", "shake");
}
```

### Screens on objects

`ui_surface` draws a screen onto every mesh of an object as an unlit picture, for computer monitors,
scoreboards, clocks and signs. The screen keeps its data bindings and animations. Use a thin box whose
front (+Z face) is the screen; its other faces show the picture too. The size is in pixels, 16 to
2,048 on each side, and defaults to 512 × 384. At most 32 surfaces can be bound at once. `ui_detach`
or `ui_hide` unbinds a surface, and **Stop** gives the object its own materials back.

```rust
fn start() {
    ui_show("scoreboard", #{ home: 0, away: 0 });
    ui_surface("scoreboard", find("Scoreboard"), 1024, 320);
}
```

### Script elements and HUD

| Function | Description |
| --- | --- |
| `ui_label(id, text)` | A text label |
| `ui_bar(id, label, value, max)` | A progress bar |
| `ui_panel(id, title, lines, buttons)` | A panel with lines of text and buttons; clicks call `on_ui(panel, button)` |
| `ui_html(id, markup)` | Any markup |
| `html(text)` | Escapes text before you insert it into markup |
| `toast(text)` | A short notification |
| `set_hud(visible)` | Shows or hides the HUD |
| `set_marker(key, x, y, z, label)`, `clear_marker(key)` | Places or removes a world marker |

Script elements are rebuilt every frame. Call them from `update(dt)` to keep them on screen.

```rust
fn update(dt) {
    ui_bar("stamina", "Stamina", get("stamina"), 100);
}
```

## Built-in screen templates

**Settings → Game UI templates** lets you override the markup of the engine's own screens. Each template
receives fixed fields:

| Template | Shows |
| --- | --- |
| `dialogue` | The dialogue box, with speaker, text and choices |
| `subtitle` | A subtitle line |
| `quest_tracker` | Active quests and objectives |
| `journal` | The quest journal |
| `save_menu` | Save and load slots |
| `skip_hint` | The cinematic skip prompt |
| `inventory` | The player's inventory |
| `container` | A chest or container |
| `status_bar` | Health, statuses, the worn tool and stamina |

## Pause menu

In an exported game, **Escape** (or the input action bound to pause) opens the pause menu: **Resume**,
**Save game**, **Load game**, **Settings**, **Controls** and **Quit to desktop**. **Settings** covers
graphics, fullscreen, vertical sync, music, effects and voice volumes, mouse sensitivity, camera shake,
motion blur and language. **Controls** lets players rebind keys and gamepad buttons. See
[Saves & settings](/docs/shipping/saves-and-settings/).

### Your own title, pause and options screens

A game can draw its own menus as screens and leave the engine's menu for settings and key bindings:

| Function | Description |
| --- | --- |
| `use_game_menu(on)` | The game draws its own pause menu: **Escape** no longer opens the engine's menu and reaches `on_key` instead |
| `open_system_menu()` | Opens the engine's menu on its **Settings** page, in an exported game |
| `pause_game(on)`, `game_paused()` | Freezes game time (physics, timers and the player) while hooks keep running with `dt` 0, and reads whether it is paused |
| `quit_game()` | Quits to the desktop; in the editor it stops Play |
| `user_settings()` | The player's settings as a map |
| `set_user_setting(key, value)` | Changes one setting and saves it, as the pause menu's **Settings** page does |

`use_game_menu` and `pause_game` reset when Play stops. `user_settings()` has these keys:

| Key | Value |
| --- | --- |
| `quality` | `"Low"`, `"Medium"` or `"High"` |
| `fullscreen`, `vsync`, `motion_blur` | `true` or `false` |
| `master_volume`, `music_volume`, `sfx_volume`, `voice_volume` | 0 to 2 |
| `mouse_sensitivity` | 0.1 to 4 |
| `camera_shake` | 0 to 1 |

Numbers outside a range are clamped. An unknown key or a value of the wrong type is a script error.

```rust
fn start() {
    use_game_menu(true);
    on("ui:resume", |e| { ui_hide("pause"); pause_game(false); });
    on("ui:options", |e| open_system_menu());
    on("ui:quit", |e| quit_game());
    // <button onclick="music" data-level="0.5">Quiet music</button>
    on("ui:music", |e| set_user_setting("music_volume", e.level));
}

fn on_key(key) {
    if key == "ESCAPE" {
        let paused = !game_paused();
        pause_game(paused);
        if paused { ui_show("pause"); } else { ui_hide("pause"); }
    }
}
```

## Limits

| Limit | Value |
| --- | --- |
| Screens per project | 64 |
| HTML per screen | 64 KiB |
| CSS per screen | 32 KiB |
| Layer | −100 to 100 |

## Automate it

| Command | Purpose |
| --- | --- |
| `ui.get` | Lists screens, or returns one screen's HTML, CSS, description, layer and sample |
| `ui.upsert` | Creates or changes a screen; fields you omit keep their value, and the sample must render |
| `ui.remove` | Deletes a screen |
| `ui.render` | Renders a screen or draft HTML with data and returns its markup and buttons, without changing anything. When the screen uses widgets it also returns `widgets`, `expanded` (the markup the game draws) and `widget_warnings` |
| `ui_widget.get`, `ui_widget.upsert`, `ui_widget.remove`, `ui_widget.render` | Manage and preview [widgets](/docs/presentation/game-ui-widgets/) |
| `font.import` | Imports a TTF or OTF font for the stylesheet |

```bash
feather-cli --connect .feather-session.json command ui.upsert --json '{
  "name": "hud",
  "description": "Health bar. Fields: health, max.",
  "html": "<panel class=\"hud-health\"><bar value=\"{{health}}\" max=\"{{max}}\" label=\"Health\"></bar></panel>",
  "css": ".hud .hud-health { width: 240px; padding: 8px; }",
  "layer": 10,
  "sample": {"health": 75, "max": 100}
}'
```

A `--verify-run` report (see [Profiler](/docs/shipping/profiler/)) has a `ui` section with the
screens on show, the screens playing their exit (`leaving`), `css_error`, `widget_errors` and the
screens drawn onto objects (`surfaces`).

See [Agents & automation](/docs/agents/overview/) for connecting a live session.
