Log inGet started

The editor UI

Everything you see when you edit a world — the menu bar across the top, the dockable panels, the play controls — is world-side Luau content, built from the same Z. widgets and the same engine APIs…

The shape of the editor

The editor surface is three layers, stacked top to bottom:

  • The top bar — always present in the editor. On the left, domain menus (Scene, Content, Render, Debug, Window, Settings): click one to drop it open, hover siblings to switch between them, click away or press Esc to dismiss. In the center, the transport (below). Everything in a menu either opens a panel or runs an action.
  • The dock — the area under the bar. Tools live here as dockable panels: tabs you can drag between regions, split side-by-side, stack into tab groups, and close. The editor boots with the dock empty so the live 3D view fills the space; tools appear only when you open them from a menu. Closing every panel returns you to a clean view of the world.
  • The world itself — always rendering underneath. The editor is an overlay, not a window around the world. F1 hides and shows the whole editor layer at once when you want nothing between you and the scene.

A few panels worth knowing exist out of the box: Viewport (the scene rendered into a dockable tab through an offscreen camera that mirrors your view), Entities (the scenegraph), Execute (a Luau console — run code against the live world), Terminal (the engine shell over the VFS), and the inspectors under Debug. The Agent Chat is a window you opt into from Window; it stays closed until you ask for it, and remembers your choice across mode changes.

The transport: reading mode at a glance

The centered / pair are state toggles — they always show what is, never what would be:

  • lit means the world is playing. Press the lit button to stop and return to edit. Unlit means you're editing; press it to play.
  • lit means the gameplay clock is stopped. In edit mode it is always lit (editing is the world held still) and locked; in play mode it becomes interactive and pauses/resumes gameplay.

One rule governs both: lit = on, press to flip. There is no button whose label describes a future state.

Edit mode and the editor's own liveliness

Edit mode freezes gameplayupdate loops, physics, the clock — but the editor UI itself keeps running. If you write editor tooling (a panel, an editor-side component), this distinction reaches your code: a component that only defines update(dt) freezes with gameplay, while the editor's own components also define editorUpdate(dt) so they stay interactive while the world is held still. Panels registered through the registry (below) don't need to care — the dock host ticks them for you.

Extending the editor from your world

The built-in menus and panels register through a registry, and your world registers through the same door:

local editor = require("@builtin::modules.api.editor.registry")
local Z = require("@builtin::modules.zui")

editor.addMenu({ id = "skyworld", label = "Sky World" })

editor.addPanel({
    id = "time_of_day", label = "Time of Day", menu = "skyworld",
    build = function(state)
        return Z.vbox({
            Z.lbl("Sun angle"),
            Z.slider("tod-angle", currentAngle(), 0, 360, { onChange = "tod:angle" }),
        })
    end,
    onCallback = function(id, value, state)
        if id == "tod:angle" then setSunAngle(value); return true end
        return false
    end,
})

editor.addMenuItem({ id = "midnight", menu = "skyworld", label = "Jump to Midnight",
                     onClick = function() setSunAngle(270) end })

Open the editor and a Sky World menu sits after the built-ins; "Time of Day" opens a real dock panel — draggable, splittable, closable like Entities; "Jump to Midnight" just runs. remove* calls take it back out; list() shows what's registered. Changes apply live — the bar rebuilds from the registry, no restart.

What the editor owns so you don't have to: docking chrome and rearrangement; scheduling (your build runs only while the panel is open — pass refresh in seconds for live data, and a closed panel costs nothing); fault isolation (a build that errors renders as an error label instead of breaking the editor); persistence across edit ↔ play (register once, it survives mode flips); and friendly re-registration (same id replaces — hot-reloading your tooling module just works; colliding with a built-in id is rejected with a warning).

Put panels where a user would look: scene for what's in the world, content for assets and files, debug for introspection, your own menu for what's distinctly yours. An item naming a menu nobody registered lands in Window with a warning rather than vanishing.

The boundary

The registry composes editor surface — menus, items, panels. What happens inside your panel is ordinary world code: the same widgets, callbacks, and engine APIs as anywhere else (see man zui for the widget vocabulary). If you need an editor capability that doesn't exist — a new docking behavior, a viewport interaction — that's an engine change to file an issue for, not something to simulate from a panel.