Skip to main content

Files

A UI is just four files in a folder. ModKit creates them for you the first time you add an app to your project.

UI files and their roles

01. The four files at a glance

FileOne-line role
uimod_manifest.jsonThe "settings card" — when does this UI open, how big is it, can it click through to the game?
index.htmlThe HTML page the game renders. The visible structure of your panel.
app.jsYour JavaScript. Reacts to triggers and talks to the game.
styles.cssPlain CSS. Controls how your panel looks.

Term: "app"

One mod can contain multiple UI apps (e.g. a clock_widget and a mood_panel). Each app is its own folder with its own four files. Picking which app is active is what the File Tree in the workspace does.


02. uimod_manifest.json — the settings card

This is the only file the game requires in order to load your panel. It is tiny. Here is the manifest from the shipped Clock Widget sample:

{
"name": "Clock Widget",
"entry": "index.html",
"display": {
"width": 1920,
"height": 1080,
"transparent": true,
"input_policy": "passthrough"
},
"triggers": [
{ "type": "always_on" }
]
}

And here is Mood Panel, which is opened on a keybind and also through a chat command:

{
"name": "Mood Panel",
"entry": "index.html",
"display": {
"width": 1920,
"height": 1080,
"transparent": true,
"input_policy": "overlay"
},
"triggers": [
{ "type": "keybind", "key": "M", "modifiers": ["Ctrl"] },
{ "type": "command", "command": "mood_panel.toggle" }
]
}

02-1. Top-level fields

FieldMeaningDefault
nameHuman-readable name of the app. Shown in lists.required
entryThe HTML file the game opens first."index.html"
displayHow the panel is sized and how it handles input.see below
triggersWhen your panel wakes up. At least one entry.required

02-2. display

Sub-fieldWhat it does
width, heightThe logical size of your panel in pixels. Internally always treated as a 1920x1080 canvas — scale your CSS accordingly.
transparenttrue lets the game show through anywhere you don't draw.
input_policyHow your panel handles mouse/keyboard. See the term box below.

Term: input_policy

  • overlayI want clicks on me, but the game keeps running. Best default for panels with buttons, drag handles, sliders, or scroll areas (e.g. Mood Panel).
  • modalI want clicks on me, and the game should pause behind me. For full-screen dialogs.
  • passthroughI never want clicks. Anything the user clicks should hit the game. For passive HUDs and decoration like Clock Widget. Do not use this for interactive panels.

02-3. triggers

A trigger is the condition that opens your panel. Each entry is { "type": "...", ...extra fields... }.

typeExtra field(s)When it fires
always_onThe panel is always visible. Best with passthrough.
keybindkey (a single key, e.g. "F1", "M") and optional modifiers (e.g. ["Ctrl"])Player presses the key with exactly those modifiers held.
commandcommand (e.g. "mood_panel.toggle")Player types the chat/cheat command.
game_modemode (string or array of mode names)The game enters one of the listed modes.
interaction_menulabel_keyA matching interaction is selected.
game_eventevent (e.g. "zoi.mood_changed")The game emits the named event.
ui_eventeventAnother UI emits the named UI event.
ui_injectionadvanced runtime fieldsThe game asks for UI to be injected at a known spot. Use only when the target slot contract is known.
manual / autoReserved for tooling. Use the explicit types above first.

When you edit the manifest through ModKit's manifest panel or modkit_write_uimod_manifest, trigger id values are generated on save if you leave them blank. If you write uimod_manifest.json directly as a raw file, every trigger must include a unique id; the game-side parser rejects triggers without one.

Important: game_mode uses mode, not modes.

The game-side manifest parser reads only the field named mode. Use either a single string:

{ "type": "game_mode", "mode": "gameplay" }

or an array:

{ "type": "game_mode", "mode": ["TopView", "ShoulderView", "Vehicle"] }

Do not write "modes": []. The game ignores that field, so the trigger will never match and the UI will stay hidden. The "gameplay" alias expands to the common in-world modes: TopView, ShoulderView, and Vehicle.

Use these values for game_mode:

ValueMeaning
gameplayRecommended default alias. Expands to TopView, ShoulderView, and Vehicle.
TopViewZoi control in top-view mode.
ShoulderViewZoi control in shoulder-view mode.
VehicleVehicle control.
PhotoPhoto mode.
FreeCameraFree camera mode.
BuildBuild Studio.
CharacterCustomizeCharacter Studio.
MapCity/map mode.
NoneCommon/lobby/no active gameplay mode.

Trigger Recipes

Use always_on for HUDs that should exist immediately:

{ "type": "always_on" }

Use keybind when the player should press a key to open or toggle the panel:

{ "type": "keybind", "key": "F1" }

For a key combination, put the key in key and the held modifiers in modifiers:

{ "type": "keybind", "key": "M", "modifiers": ["Ctrl"] }

Important: "key": "Ctrl+M" does not work.

The game-side parser looks key up as one key name and does not split "+", so "Ctrl+M" resolves to an invalid key and the trigger never fires — silently, with no in-game error. Write the key and the modifiers separately.

modifiers must be an array of strings, and only "Ctrl", "Shift" and "Alt" are recognised — spelled exactly like that, since the game matches them case-sensitively. Matching is exact: with "modifiers": ["Ctrl"] the trigger fires on Ctrl+M but not on Ctrl+Shift+M, and a keybind with no modifiers does not fire while Ctrl, Shift or Alt is held.

Use command when the player or another tool should open it by command:

{ "type": "command", "command": "mood_panel.toggle" }

Use game_event / ui_event only when you know the event name emitted by the game or another UI:

{ "type": "game_event", "event": "zoi.mood_changed" }

Use interaction_menu for advanced menu injection. The label field is label_key, not label:

{ "type": "interaction_menu", "target": "character", "label_key": "My Action" }

Your JavaScript receives the trigger through one callback:

inzoi.trigger.onActivated(function (t) {
if (t.type === "keybind" && t.key === "F1") {
// Open, close, or run your UI action here.
}
});

You can declare multiple triggers in the same manifest. Mood Panel uses both keybind and command so the player can choose how to open it.


03. index.html — the page

A UI's index.html is a regular HTML page. The only conventions:

  • It should load the SDK so window.inzoi.* is available in your scripts.
  • It usually ends with <script src="app.js"></script> so your code runs after the DOM is ready.

Minimal example (this is exactly what ModKit scaffolds):

<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>My App</title>
<link rel="stylesheet" href="styles.css">
<script src="coui://inzoi/cohtml.js"></script>
<script src="coui://inzoi/mod/inzoi-sdk.js"></script>
</head>
<body>
<div id="root">Hello, inZOI</div>
<script src="app.js"></script>
</body>
</html>

Why these two coui:// scripts?

coui:// is the in-game UI engine's equivalent of https://. Both files live next to the game — you do not bundle them, you just reference them.

  • cohtml.js boots the engine binding (engine global, view lifecycle callbacks). Required: without it the view stays in Loading state and never receives input.
  • inzoi-sdk.js exposes the high-level window.inzoi API (inzoi.cli, inzoi.trigger, inzoi.view, …) on top of engine.

Always load them in this order, before your own scripts. In the editor preview, ModKit injects equivalent mocks so your code behaves identically.


04. app.js — your code

app.js is where you actually do things. The pattern in every shipped sample is the same:

  1. Get references to your DOM elements.
  2. Wire up DOM events (button clicks, mouseenter, etc.).
  3. Subscribe to UI triggers with inzoi.trigger.onActivated.
  4. Call game commands with inzoi.cli.execute.

You will see this pattern in detail on the Scripting page.


05. styles.css — appearance

This is plain CSS. The in-game UI engine renders it natively, so all the usual properties work — flexbox, transitions, custom properties, etc.

Two things to keep in mind:

  • The canvas is treated as 1920x1080. If you want pixel-perfect results, design your CSS against that size.
  • If display.transparent is true, the body background should also be transparent (background: transparent; or just no background) so the game shows through.

06. Multiple apps in one mod

If your mod ships more than one app, ModKit also creates a small uimod_apps.json at the ui/ root that lists them:

{
"apps": [
{ "name": "clock_widget", "default": true, "enabled": true },
{ "name": "mood_panel", "default": false, "enabled": true }
]
}

You almost never edit this by hand. ModKit updates it when you add or remove apps from the workspace.