Dicey Table

Item Editors

UI Layout Editor

Design a game's HUD visually, style widgets and regions, and preview it on real desktop, tablet, phone and VR screens beside the table's own controls.

On this page10 sections

A UI layout is a game's HUD written down as data: the state it reads, and the widgets that show it. Scripts fill in the state and the platform draws the widgets off the table — on the desktop, on a phone and in VR — so players never have to read status text floating over the felt. This tab is the visual editor for one layout. The JSON it writes is exactly the JSON your scripts use; there is no second format.

Open it from the Game Editor:

  • the Game row's SCENE UI block (beside SCENE SCRIPTS) — Edit on an attached layout, or + New UI layout;
  • the Hierarchy's UI Layouts (N) folder — select a layout and press Edit.

See the tab strip conventions — closing, cycling and mounted-but-hidden tabs apply here too. Unlike most tabs it has no Save button: every change goes straight into the scene draft, like the Material editor, so Save Draft and Undo cover it.

Vocabulary#

Term Means
Layout One HUD design: declared state keys plus widgets. Lives in the scene's layout library.
Widget One piece of HUD: a status line, an action strip, a prompt, a player panel…
Region Where a widget sits on a flat screen (upper, lower-end, center, the question sheet…).
Attachment A seat, entity or zone a widget is drawn at (a billboard, and its place in VR).
State key A named value a script sets with ui.set and a widget reads with {key}.

None of these are engine Components, and a layout is not an Entity: it is a record in the scene document, shared by every variant.

Library and attachment#

Layouts live in a library shared by every variant, the same way scripts do — fix a layout once and every variant that shows it gets the fix. Which layouts a variant shows is its own choice: the SCENE UI block writes the attachment for the variant you are editing, and the variant scope bar decides whether that lands in the base or in this variant only. The usual pattern is a Scripted variant that attaches the HUD and an Unscripted one that does not.

A project holds at most 32 layouts and a variant attaches at most 8. At either limit, + New UI layout, Duplicate and attaching are disabled and the block says why; a new layout made while its variant is full goes into the library unattached. One layout may be at most 256 KB as JSON: an edit, an Apply JSON or an import that would pass that is refused with a message, and nothing is written.

When the game's scripts start, the host materialises the attached layouts:

  1. every declared default is written (table keys once, seat keys for every seat);
  2. the furniture widgets are shown — status, actions, player panel, counter, meter, log, hint and panel;
  3. the moments wait for a script — banner, prompt, choice, result, timer and card spotlight appear only when a script calls ui.show("id") (or ui.prompt) — see Game UI.

Use a widget's Condition to keep furniture hidden until it applies. A widget whose audience is actor is never shown at start (nobody has acted yet); a script shows it from ui.onAction.

A layout with an error is never shown at the table, and the pack will not publish until it is fixed. Warnings do not stop anything.

The three columns#

Layout (left) — the name and the default font, then the widget palette and the region map: a 3×3 grid for the flat screen, plus Sheet (questions: prompts and choices) and Attached only (widgets drawn at a seat, entity or zone). Choose a kind and press + Add widget; every kind starts valid. Drag a widget between regions, or select it and press ⤵ on the region you want. Widgets move between regions, never to screen positions, so the same layout fits every screen; the widget's Style section sizes, aligns and nudges it within its region. Under the map, Region layout sets how one region lays out its widgets (see Region layout).

Canvas (centre) — the layout drawn by the real renderers, at the real size of a real screen. The frame is the screen in CSS pixels, scaled to fit, and the HUD inside it is laid out exactly as the table lays it out on that screen: the same desktop, tablet or phone layout, chosen the same way (a touch screen whose shorter side is under 600 px is a phone), with the same per-device placement and style.

Device What you see
Desktop 1280×720, Desktop 1920×1080, Laptop 1440×900 The table HUD as a desktop player sees it
Tablet 820×1180, Tablet landscape 1180×820 The touch layout: the upper region folds into a pill, the side and lower regions stack above the hand, questions become a bottom sheet
iPhone 390×844 and 844×390, Android 412×915 and 915×412, Small phone 360×740 and 740×360 The compact phone layout: the status pill, upper-end as a small stack under the dice button, every actions widget docked bottom-right, the log behind a bottom-left tab, and questions in a narrower bottom sheet
VR tray The head-tethered tray and the first dialog, as the headset draws them, flattened

Table chrome (on by default) draws the table's own controls in the frame as placeholders, at the size and place the table draws them on that screen: the room badge, toolbar, connection pill, utility buttons, dice, player pills and chat feed on a desktop; the menu button and room badge on a tablet; the menu button, avatar rail, status squares and dice on a phone; and the hand drawer when you view as a seat (collapsed to its handle on a phone). They sit above the HUD, as at the table, so you can see what a widget would slide under. With it on, each region's box is outlined too.

The toolbar also picks the preview state, view as (the host, a spectator, or a seat — private widgets and private seat values appear only for their own seat), which moments to draw (the selected one, all, or none) and the button state (below). The selected widget is outlined in the canvas, and clicking a widget there (anywhere but one of its buttons) selects it; a selected widget can be resized and nudged right there (see Drag to resize and nudge). Pressing a button in the canvas runs no script; the line under the canvas shows exactly what ui.onAction would receive.

Inspector (right) — five sub-tabs: Widget, State, Previews, Problems and JSON.

Widgets#

The Widget sub-tab edits the selected widget:

  • Widget — its id (what scripts name), kind, audience (only all is public; every other audience travels privately to its viewers) and whether spectators may press it.
  • Placement — region, order within the region, and an optional attachment.
  • Per device — place the widget differently on one kind of screen: Desktop, Tablet or Phone can each take another region or Hidden, and an actions widget can be folded into a menu button (one button that opens its buttons). Each setting changes only its own screen, so tightening the phone layout never moves anything on a desktop. A prompt or choice is never hidden.
  • Text — the fields the kind uses. Text takes bindings: {phase}, {seat.force} (the viewer's own seat), {key|fallback}, plus **bold**, *italic* and [color=red]…[/color]. Insert binding appends a declared key to the field you choose. Values are always inserted as text, never markup.
  • Condition — show the widget only while rows hold (is, is not, is one of, is set, is empty, variant is one of). A condition that nests groups is edited in the JSON sub-tab.
  • Timing — how long it stays once shown, and what ends it.
  • Theme — tone, accent (a palette name or #rrggbb, contrast-clamped), an icon from the HUD icon list, an icon image (a game picture drawn in place of the icon), a font from the self-hosted list, density, emphasis, and for prompts, choices, results, panels and player panels a header image.
  • Style — the widget's own size, place, spacing, colours, border, type and button layout (see Style).
  • Kind sections — buttons, choices, counters and badges (each with an icon and an icon image, so a game can show its own stat symbols), prompt timing, log length, timer length, a panel's basic widgets as JSON, and for a card spotlight the card (Entity id, Card id, the required Card label), Show counters, and its stat chips (label, value, Change: same white, up green, down red, icon and icon image — a chip with an icon or icon image draws that symbol instead of its label, which stays its accessible name and tooltip; at most 8). A script usually sets the card when it shows the spotlight — see Card spotlights.
  • Search box (choices) — Auto shows a filter box above the options when there are more than 12, On always, Off never; Search placeholder sets its hint when it is On. The canvas preview draws the box (type into it to try the filter); the VR frame draws the letter tabs a headset player gets instead. The typed text stays on the player's screen: a pick is still an option id.

Buttons and their states#

Every button has five states: default, hover, pressed, active (toggled) and disabled. The Buttons switcher above the canvas does two things at once: it draws the selected widget's buttons in that state, and it chooses which state the button's label, icon, images, tone, accent and emphasis fields edit. Default writes the button itself; every other state stores only what differs and inherits the rest. Touch has no hover; VR maps hover to the ray or fingertip.

Enabled when and Active when bind a state key: a disabled button never sends an action, and the host refuses one anyway. A press sends data only — the widget id, the action id and, for a choice, the picked values.

Style#

The Style section edits the widget's style: a closed list of keys, never CSS. Leave a field empty to keep the platform's value; the placeholder shows what applies.

  • Applies to — All devices edits the widget's own style. Desktop, Tablet or Phone edits that screen's override, which is merged over All devices on that screen only (the placeholders then show the All devices values). Pick the matching canvas frame to see it.
  • Size — width, min width, max width, height and max height, each in px or % of the screen; a width can also be auto (fit the content) or fill (as wide as the region), a height auto. A widget is never wider than the screen or taller than 85% of it; content past a fixed height scrolls.
  • Position in the region — Align (start, center, end, stretch) and a Nudge of up to 200 px each way.
  • Spacing — Padding and Margin, for all sides or per side (above, below, start, end), and the Gap between the widget's own buttons, chips and rows.
  • Colours and border — background (a palette name or #rrggbb, with a colour well) and its opacity, text colour (kept readable on the background: too-close colours are lightened or darkened), border width, style and colour, corner radius and shadow.
  • Type — font size (10-32 px; headings scale with it), weight, text alignment and Max lines (each paragraph of the body is cut there).
  • Buttons (actions, prompts and choices) — a row or a column, wrap or one scrolling line, size (sm, md, lg), how each button shows its icon and label (beside, under, icon only, label only) and where they sit in their row.

Reset style clears the style at the chosen scope. A question (prompt or choice) keeps the sheet's size and place; only its look is styled. VR ignores style.

Region layout#

Region layout, under the region map, sets how one region lays out its widgets — the layout's regions map: Direction (a column, the platform's, or a row), Align, Justify (start, center, end, between), Gap, Wrap, Max width, Max height and a Nudge. Pick the region first. Applies to works as in Style (and is the same setting): All devices edits the region's own keys, Desktop, Tablet or Phone that screen's keys, merged over them there, with the All devices values shown as placeholders. When several attached layouts style one region, the first widget drawn there decides. Reset region clears the keys at the chosen scope (at All devices the per-screen keys stay).

On a phone, the compact layout deals widgets into its own corner slots, and a slot takes a region's style when every widget in it was placed in that one region:

Phone slot Takes the style of
The stats stack under the dice button upper-end
The centre center
The action dock (bottom-right) the region every actions widget in it was placed in, when they all share one
The card spotlight row the region every card in it was placed in, when they all share one
The status pill, the log tab never: they are one-tap platform controls

There a region style stays inside the slot's corner: the max sizes can only shrink it, and the nudge only moves it away from the screen edges it sits against. Preview it in a phone frame.

Drag to resize and nudge#

With a widget selected, the canvas draws resize handles on its edges and corners. Drag a handle to set its Width and Height in px; drag the widget itself (anywhere but one of its buttons) to nudge it. Both write the Style fields at the current Applies to scope (All devices writes style, a screen class writes that screen's style), so pick a matching frame to watch it. While dragging, a readout shows the size or the nudge; hold Shift for 8 px steps, press Escape to cancel. A value that reaches its limit (a nudge of ±200 px, the screen's width, 85% of its height, 16 px at the smallest) stops there and its handles turn amber. Releasing writes the change once, so one Undo reverts the whole drag. With the canvas focused, the arrow keys nudge the selected widget by 1 px (Shift: 8 px).

Images and fonts#

Images come only from this project's pre-loaded game files: PNG, JPEG or WebP files the published manifest declares in assets, because every player draws them. The picker lists exactly those, and the editor and the publish scanner apply the same rule, so an image the editor accepts always publishes. Files under media/ (listing art), originals/, rules/ and thumbnails/, a deck's card source images, and files you switched to load on demand are not offered and are refused. URLs, data:, SVG and paths outside the project are refused as well.

The header image, the theme's icon image and a counter's or badge's icon image also offer each string state key as a binding — {seat.sideIcon} for a per-seat key, {phaseIcon} for a table key. A bound image shows whatever path the game's script sets, so one player panel can show every seat its own faction or side symbol. The value is drawn only when it is itself one of this project's PNG, JPEG or WebP paths (never a URL), and a private per-seat value only for its own seat. A fallback image ({seat.sideIcon|ui/side-neutral.png}) can be typed in the JSON sub-tab; it is checked like any other image. Preview fixtures set the values, so the canvas shows the symbols.

Fonts come from a fixed, self-hosted list (platform, serif, display-classic, display-bold, sci-fi, mono, retro, handwritten).

State keys#

The State sub-tab declares what bindings and scripts read. Each key has a type, a scope (one value, or per seat — read as {seat.key}), an optional default, and for per-seat keys a private flag: a private value travels only to its own seat, so an instruction like "play a card or pass" is not shown to the opponent. A script's ui.set("howTo", text, seat) is routed privately for a key declared private here.

Preview states#

The Previews sub-tab holds named fixtures — state values and whose screen to show — such as "Red's turn, mid-game, on a phone". Preview this loads one into the canvas. Fixtures are saved with the layout for the next author and are never sent to a table.

Problems#

The Problems sub-tab lists what the publish scanner would say: errors (an unknown kind, font or icon; a broken shape; a style or region-layout key that is not in the vocabulary; an image that is not a pre-loaded game file; a layout over 256 KB as JSON; colliding widget ids across attached layouts; more than 32 layouts in the project or more than 8 attached to one variant) and warnings (a binding that reads an undeclared key or the wrong scope; a condition naming an unknown variant; buttons no attached script appears to handle). The same layout errors appear in the editor's status-bar problems list. Show selects the widget a problem is about.

JSON, import and export#

The JSON sub-tab is the layout as text. Edit it and press Apply JSON; the layout's id is kept. Export JSON downloads <id>.layout.json, and Import file… replaces the layout with one from disk. A *.layout.json opened in the Plain File editor is checked against the generated layout schema as you type.

Did this page answer your question?