Skip to main content
The ui module gives your script its own settings window inside the menu. You declare controls once; the menu builds real widgets from that declaration and keeps them alive. There is no render callback — the menu draws on the render thread, your script runs on its own, and the two only exchange declarations and events. Values live in the same store the rest of the cheat uses, so a script’s controls save into configs, come back when one is applied, and carry hotkeys exactly like native controls do. You write none of that.

Overview

Pages and groups

Tabs down the left, panels in up to two columns.

Controls

Switches, sliders, combos, colours, inputs, buttons.

Reactivity

Assign a function to visible, disabled, or label and forget about it.

Persistence

How values are keyed, saved, and restored.

Quick start

The window opens from the settings button on your script’s card in the Scripts tab. The button appears by itself as soon as your script declares a page.

The window

Your script gets one window, laid out like the menu itself:
  • a header with the script’s name and a close button
  • a rail of icon-only tabs down the left, one per page — it scrolls when a script declares more tabs than fit
  • up to two columns of panels, 240px each
The window has two widths and one height. A page whose groups fit in one column makes a narrow window; a page that fills both makes a wide one. Height is fixed — long pages scroll inside their column rather than growing the window.
The window drags by its header, closes with Escape or the ×, and is deliberately not dismissed by clicking into the menu behind it — you are meant to use both at once.

Pages and groups

ui.page


Creates a tab in your window, or returns the existing one. Get-or-create, so running it again on a reload never duplicates anything.
string
required
Names the tab and derives the page’s id.
string
Tabler icon name, as used by the native tabs: "target", "eye", "code", "keyboard". Defaults to "adjustments".
string
Pins the path values save under. See Persistence.
number
Position among your other tabs. Lower comes first.

container.group


A panel. On a page this is the normal way to lay things out; inside a group it nests. Also get-or-create.
boolean
Puts a switch in the panel header. The group then behaves as a control too: group.value, group.label, group.on("change").
boolean
Starting state of the header switch. Only with toggle.
boolean
Gives the header a hotkey chip. Only with toggle.
"left" | "right"
Forces the column. Left alone, groups are split down the middle in declaration order — so one group stays one column and the window stays narrow.

container.row


Lays its children out side by side instead of stacked.
Controls can also be declared straight on a page. They land in the column with no panel around them — useful for a caption or a single button above the panels.

Controls

Every factory below exists on pages, groups, and rows alike.

Slider options

number
required
number
required
number
Values snap to this. 0 (default) means no snapping.
number
Decimals shown. 0 (default) makes it an integer slider, which also decides how the value is stored.
string
Appended to the printed value: "°", " ms".

Combo and multi

The value is the item, not its index. Replacing the list keeps the selection by value: an item that survived stays picked wherever it moved to, one that is gone falls back to the first entry — delivered as a normal change.
In TypeScript the item list types the value: combo("Mode", ["Legit", "Rage"]) is a Control<"Legit" | "Rage">, with no as const needed.

Control handle

Buttons

style is "normal", "accent", "danger", or "dashed".

Reactivity

Assign a function to visible, disabled, or label and it re-runs whenever anything it read changes. No diffing, no re-render — only the property you fed is updated.
The same tracking powers three helpers:
key.active is tracked too, so a bind’s state can drive a label or a visibility rule: label = () => (ctl.key.active ? "ON" : "OFF").

Hotkeys

Pass key to any control to give it a bind chip.
The user binds the key in the menu: left-click the chip, then press any key, or click the chip with a mouse button. Right-click clears it, Escape cancels. The bound key is saved with the config; the mode comes from your code.
key.key is read-only — a script cannot bind a key on the user’s behalf yet.

Persistence

Every control’s value is stored under a path built from where it sits:
Each segment defaults to the label, slugified ("Double tap" becomes double_tap). Renaming a label is free. Renaming a group or page moves every id below it, and changing an explicit id orphans what configs already hold — so pass id for anything you expect to rename.
Values under a path nothing claims are kept, not dropped: unloading a script and saving a config does not strip its settings, and declaring the old path again recovers them.
Opting out:
text() is never saved, whatever persist says — a caption belongs to the script, not to a config.
Two controls whose paths collide are not merged: the second is suffixed (fov_2) and the host logs a warning. Pages and groups are get-or-create, so the same title twice is the same container.

Window and config events

When a config is applied, every value is refreshed first, then each control whose value actually moved fires its change, and finally config fires once. That ordering is the point: a change handler reading a neighbouring control sees the config’s world, and logic that must run once per config goes in config.

Lifecycle

Callbacks are delivered on the script thread, in order. Nothing here is ever called from the render thread, so a handler can do real work without stuttering the menu.

Not supported yet

ui builds your own window only. There is no way to find, read, or write the cheat’s own controls from a script yet.
Controls carry no hover text. Use text() for a line of explanation instead.
min, max, step, precision, and unit are fixed when the control is created. Only label, visible, disabled, items, and the value can change.
key.mode is yours, key.key is the user’s.

Recipes

A page that reshapes itself

Per-weapon settings from a list

A debug tab that never touches configs