# Tutorial: first UI

Runtime UI is immediate-mode: each frame you rebuild widgets through the bound UI facade (`const ui = hi.ui();`). Nest controls in stacks (`ui.vStack` / `ui.hStack`) and always `defer ui.end(scope)`.

Deep reference: [User interface](/docs/guides/user-interface.md), [UI layout](/docs/guides/ui-layout.md), [UI widgets](/docs/guides/ui-widgets.md), [UI and editor](/docs/systems/ui-and-editor.md), [Motion Kit](/docs/systems/motion-kit.md).

## Choose an owner

| UI kind | Owner | Lifetime |
|---|---|---|
| Screen / level-specific panels | Scene entity `update` | Dies with the scene |
| Cross-scene HUD / pause / settings chrome | `GameSubsystem.onTick` | Survives scene replace |

Do not hang tween timelines on `UiContext` — store drivers on entity or subsystem state (`hi.motion`).

---

## Scene-owned UI entity

```zig
const hi = @import("hikari_game");

const HudLogic = hi.defineComponent(.{
    .name = "hud_logic",
    .storage = .embedded,
    .data = struct {
        pub fn update(_: *@This(), _: hi.ActorContext, _: *const hi.TickContext) void {
            const ui = hi.ui();

            const panel = ui.vStack(.{
                .position = .{ 16, 16 },
                .width = .{ .points = 240 },
                .padding = 12,
                .spacing = 8,
                .surface = .surface,
            });
            defer ui.end(panel);

            ui.text("Scene HUD", .{ .role = .secondary });

            if (ui.button("Reload", .{
                .id = "scene-hud.reload",
                .variant = .primary,
            })) {
                hi.world().requestSceneReload();
            }
        }
    },
});

pub const HudEntity = hi.defineActor(.{
    .archetype = "scene_hud",
    .components = .{HudLogic},
});
```

Place `"archetype": "scene_hud"` in the scene (no render/physics required). Rebuild, Play, and click the button.

Stable `.id` values matter when labels repeat — hit-testing keys off the id.

Sample overlays: session HUD profiler / G-buffer toggles in `hikari-games/example/src/session_ui.zig` (settings sheet). Scene-placed `debug` (`debug_entity.zig`) is an empty marker.

---

## Session HUD

From `GameSubsystem.onWorldAttach`, reserve capacity for each World:

```zig
const ui = hi.ui();
ui.reserveCapacity(64 * 1024, 64, 48);
ui.setDensity(.compact);
```

From `onTick`, draw the same stack pattern with `hi.ui()`. Full sample: `hikari-games/example/src/session.zig` — compact session card plus an optional non-modal settings sheet (graphics knobs, keybind legend) so the scene stays visible while tuning.

Modals: finish the page stack, then open `beginModal` at the root (see sample quit confirm). Prefer a second positioned `vStack` for settings/tools that should not dim the world.

---

## Layout tips

- Root stacks default near `{16, 16}`; use `.width = .{ .percent = 0.30 }` with `min_width` / `max_width` for responsive panels.
- `justify` / flex reflow uses previous-frame geometry for hit-testing — expect a one-frame lag when a layout first appears.
- Game theme is `Theme.runtime()`; do not copy the editor retained theme onto the runtime UI context.

---

## Verify

1. Play with a scene HUD entity or the sample session panel.
2. Confirm entity UI clicks work only while Play (Edit freezes entity updates; `GameSubsystem.onTick` still runs in editor Play).
3. Replace the scene — session HUD should remain; scene entity UI should disappear.

## Next

- [First input action](/docs/tutorials/first-input.md)
- [First session services](/docs/tutorials/first-session-services.md)
