UI
The @yagejs/ui package provides screen-space UI powered by
Yoga flexbox layout. Build menus, HUDs, and
overlays with a fluent builder API.
For a React-based alternative, see UI (React).
UIPlugin Setup
Section titled “UIPlugin Setup”import { UIPlugin } from "@yagejs/ui";
engine.use(new UIPlugin());The plugin depends on @yagejs/renderer.
UIPanel
Section titled “UIPanel”UIPanel is the root UI component. Add it to an entity to create a UI tree.
import { UIPanel, Anchor } from "@yagejs/ui";
const ui = this.spawn("hud");ui.add(new Transform());ui.add(new UIPanel({ anchor: Anchor.TopLeft, direction: "column", gap: 8, padding: 16, background: { color: 0x000000, alpha: 0.7, radius: 8 },}));Panel options:
| Property | Type | Default | Description |
|---|---|---|---|
anchor | Anchor | — | Screen-space position |
offset | { x, y } | — | Pixel offset from anchor |
direction | "row" | "column" | "column" | Flex direction |
gap | number | — | Space between children |
padding | number | Padding | — | Inner padding |
alignItems | string | — | Cross-axis alignment |
justifyContent | string | — | Main-axis alignment |
overflow | "visible" | "hidden" | "visible" | Overflow behavior |
background | BackgroundOptions | — | Color or texture background |
layer | string | — | Render layer name |
visible | boolean | true | Initial visibility |
Anchoring
Section titled “Anchoring”The Anchor enum positions panels relative to the screen:
import { Anchor } from "@yagejs/ui";
Anchor.TopLeft Anchor.TopCenter Anchor.TopRightAnchor.CenterLeft Anchor.Center Anchor.CenterRightAnchor.BottomLeft Anchor.BottomCenter Anchor.BottomRightAdd an offset to fine-tune position:
new UIPanel({ anchor: Anchor.TopRight, offset: { x: -16, y: 16 },})Layer space and positioning
Section titled “Layer space and positioning”A panel’s position comes from two independent choices: which layer it
lives on (screen-space HUD or world-space overlay) and its
positioning option (viewport-anchored or Transform-driven).
positioning: "anchor" (default)
Section titled “positioning: "anchor" (default)”anchor resolves against the viewport (virtualSize), offset is a
pixel nudge. This is the classic HUD / menu behavior and the default
for a reason — it’s what HUDs want.
positioning: "transform"
Section titled “positioning: "transform"”The panel’s root container is positioned at entity.get(Transform).worldPosition
in the target layer’s local coord space, and anchor is reinterpreted as
the pivot on the panel itself:
Anchor.Center→ panel’s center sits at the Transform.Anchor.BottomCenter→ panel’s bottom-center sits at the Transform (the natural “hovers above this entity” primitive for nameplates and health bars).
offset is still a pixel nudge, applied after the pivot. The entity
must have a Transform or the panel throws at add time.
This option is orthogonal to the layer’s space:
- Screen-space layer +
positioning: "transform": pair withScreenFollowfrom@yagejs/renderer.ScreenFollowwritescamera.worldToScreen(target) + offsetto the Transform each frame (the offset is in screen pixels, applied after projection), so the UI tracks a target entity but stays axis-aligned and constant-size regardless of camera zoom or rotation. This is the canonical billboard pattern. - World-space layer +
positioning: "transform": the UI is pinned to a real world coordinate and scales / rotates with the camera like any other world object. Useful for genuinely diegetic UI — a sign in the world, an LED on a machine.
Entity-anchored UI with ScreenFollow
Section titled “Entity-anchored UI with ScreenFollow”The common “nameplate above an enemy” pattern:
import { ScreenFollow } from "@yagejs/renderer";
class Enemy extends Entity { setup(params: { x: number; y: number; label: string; camera: CameraEntity; }) { this.add(new Transform({ position: new Vec2(params.x, params.y) })); this.add(new Health({ max: 100 }));
// Body, nameplate, and HP bar are all siblings under this entity. // Parenting expresses "these belong to this enemy" structurally — // so when the enemy is destroyed, cascade-destroy cleans the UI // children up automatically. Positioning still flows through // ScreenFollow for the UI siblings so they stay axis-aligned and // constant-size under camera zoom/rotation, regardless of parenting. this.spawnChild("body", EnemyBody, { color: 0xff6b6b }); this.spawnChild("nameplate", EnemyNameplate, { target: this, camera: params.camera, label: params.label, }); }}
class EnemyNameplate extends Entity { setup(params: { target: Entity; camera: CameraEntity; label: string; }) { this.add(new Transform()); this.add(new ScreenFollow({ target: params.target, camera: params.camera, offset: new Vec2(0, -40), // 40 screen px above the target, at any zoom })); const panel = this.add(new UIPanel({ positioning: "transform", anchor: Anchor.BottomCenter, padding: 4, background: { color: 0x000000, alpha: 0.6, radius: 4 }, })); panel.text(params.label, { fontSize: 11, fill: 0xffffff }); }}The offset is in screen pixels — 40 world units above at zoom=1 and
40 screen pixels above at zoom=2 are different things, and screen-pixel
offsets are what nameplates almost always want. See the world-ui
example for a runnable demo with zoom and rotation controls.
Add text to a panel with the builder API:
const panel = entity.get(UIPanel);
const label = panel.text("Score: 0", { fontSize: 24, fill: 0xffffff, fontFamily: "monospace",});
// Update text laterlabel.setText("Score: 100");label.setStyle({ fill: 0x00ff00 });Pixel-art text & the resolution gotcha
Section titled “Pixel-art text & the resolution gotcha”For a UIText directly (or via the React <Text>), two extra props
control rasterisation:
import { UIText } from "@yagejs/ui";
// Crisp pixel-art text — draws pre-baked glyph quads instead of a// blurry bilinear-sampled canvas texture. `true` bakes a dynamic font// from the style; `{ font }` uses an installed/loaded bitmap font.new UIText({ children: "SCORE", bitmap: true, style: { fontFamily: "monospace", fontSize: 12 } });new UIText({ children: "READY", bitmap: { font: "PressStart", size: 16 } });
// Per-text canvas resolution.new UIText({ children: "HUD", resolution: window.devicePixelRatio });Word-wrap and the truncate?: "clip" | "ellipsis" overflow modes work
the same on the bitmap path as on canvas text.
Buttons
Section titled “Buttons”const btn = panel.button("Start Game", { width: 200, // optional — omit to shrink-to-content height: 50, // optional — omit to shrink-to-content background: { color: 0x4444aa, radius: 6 }, hoverBackground: { color: 0x5555cc, radius: 6 }, pressBackground: { color: 0x333388, radius: 6 }, textStyle: { fontSize: 18, fill: 0xffffff }, onClick: () => { engine.scenes.push(new GameScene()); },});
// Disable/enablebtn.setDisabled(true);
// Change labelbtn.setText("Loading...");Buttons support three background states: default, hover, and press.
Omitting width and/or height lets Yoga shrink the button to fit its
content, with a small default padding around the label. Pass explicit
dimensions when you need a fixed-size button (e.g. for a grid of
equally-sized toolbar buttons).
UIButton is itself a flex container — .addElement() accepts any
UIElement (text, image, nested panels) for icon + label compositions:
import { UIImage } from "@yagejs/ui";
const saveBtn = panel.button("Save", { onClick: () => {} });saveBtn.addElement(new UIImage({ texture: iconTex, width: 16, height: 16 }));Nine-Slice Buttons
Section titled “Nine-Slice Buttons”Use a texture background for scalable button artwork:
panel.button("Play", { width: 180, height: 48, background: { texture: buttonTexture, mode: "nine-slice", nineSlice: { left: 12, top: 12, right: 12, bottom: 12 }, }, onClick: () => { /* ... */ },});Additional Elements
Section titled “Additional Elements”UIPanel provides builder methods for .text(), .button(), and .panel().
For other elements, create them directly and add via addElement:
import { UIImage, UIProgressBar, UICheckbox } from "@yagejs/ui";Images
Section titled “Images”const img = new UIImage({ texture: iconTexture, width: 32, height: 32, tint: 0xffffff,});panel.addElement(img);Progress Bars
Section titled “Progress Bars”const bar = new UIProgressBar({ width: 200, height: 20, value: 0.75, // 0–1 trackBackground: { color: 0x333333 }, fillBackground: { color: 0x44cc44 },});panel.addElement(bar);
// Update valuebar.update({ value: 0.5 });Checkboxes
Section titled “Checkboxes”const cb = new UICheckbox({ label: "Fullscreen", checked: false, size: 24, boxColor: 0x666666, checkColor: 0x44cc44, onChange: (checked) => { console.log("fullscreen:", checked); },});panel.addElement(cb);Nested Panels
Section titled “Nested Panels”Build complex layouts with nested panels:
const menu = entity.get(UIPanel);
// Header rowconst header = menu.panel({ direction: "row", gap: 12, alignItems: "center" });header.text("Settings", { fontSize: 28, fill: 0xffffff });
// Content columnconst content = menu.panel({ direction: "column", gap: 8, padding: 12 });content.text("Volume", { fontSize: 16, fill: 0xaaaaaa });
// Button rowconst buttons = menu.panel({ direction: "row", gap: 12 });buttons.button("Save", { width: 100, height: 40, onClick: () => { /* ... */ } });buttons.button("Cancel", { width: 100, height: 40, onClick: () => { /* ... */ } });Visibility
Section titled “Visibility”Toggle panel visibility at runtime:
const panel = entity.get(UIPanel);panel.visible = false; // hidepanel.visible = true; // showChild elements also support visibility:
label.visible = false;btn.visible = true;Absolute Positioning
Section titled “Absolute Positioning”Every element accepts position, left, top, right, bottom via the
shared LayoutProps. Use them to lift an element out of its parent’s flex
flow and pin it against the parent’s content box — handy for overlays,
badges, and HUD markers.
// Pin a notification badge to the top-right of its parent.const badge = panel.panel({ position: "absolute", top: 8, right: 8, padding: 4, background: { color: 0xff3344, radius: 8 },});badge.text("3", { fontSize: 12, fill: 0xffffff });The parent must be position: "relative" (the default) for absolute
children to resolve against it. position: "absolute" children do not
contribute to the parent’s main-axis advance, so siblings keep their
original layout.
| Property | Type | Description |
|---|---|---|
position | "relative" | "absolute" | Default "relative". |
left / top / right / bottom | number | Pixel offset from the named edge of the parent’s content box. |
Background Options
Section titled “Background Options”Backgrounds can be a solid color or a texture:
// Solid color with rounded corners{ color: 0x222222, alpha: 0.9, radius: 8 }
// Texture (nine-slice for scaling){ texture: panelTexture, mode: "nine-slice", nineSlice: { left: 16, top: 16, right: 16, bottom: 16 },}