Violet

Menu

On this page

The core.ui module lets scripts build their own settings UI in the web menu. Every constructor takes a single options table and returns an element handle you can read, write, and mutate at runtime. Settings persist across sessions and are also reachable through core.get_setting() / core.set_setting().

lua
local enabled = core.ui.checkbox{ id = "enabled", label = "Enable", panel = "General", default = false }

function on_tick()
  if enabled:get() then
    -- ...
  end
end
IDs, tabs & sandboxing
  • IDs are namespaced per script as lua.<script>.<id> (e.g. id = "enabled" in my_script.lua becomes lua.my_script.enabled). An ID you pass that already starts with lua. is kept as-is. This keeps scripts from colliding with each other or with core settings. Prefer the handle (elem:get()) so you never have to type the full ID; if you use core.get_setting, pass the full namespaced ID.
  • Custom tabs are allowed. Omit tab to land in the shared Lua tab, or pass any name to create your own top-level tab. Reserved/core tab names (autos, combat, hacks, items, map, macros, keybinds, packets, debug, settings, player, admin) are rejected. Give your tabs an icon or fold several into a submenu with core.ui.tab.
  • Idempotent. Re-running a constructor with an existing ID returns a handle to the existing element instead of creating a duplicate, so it's safe to build your UI at the top of the script.

Common options

Every constructor accepts these keys in its options table:

KeyTypeNotes
idstringRequired. Namespaced to lua.<script>.<id>.
labelstringDisplay label.
panelstringRequired. Card within the tab.
tabstringTop-level tab. Defaults to "Lua".
sectionstringIn-page sub-tab this panel belongs to (see Organizing your UI).
tooltipstringHover text.
orderintSort order within the panel (lower first).
descriptionstringPanel subtitle (card description).
visible_whenstringID of another element; this one is hidden unless that element is truthy. Namespaced like id.
hiddenboolStart hidden (toggle later with :set_visible).
disabledboolStart non-interactive (toggle later with :set_enabled).
dangerousboolHidden unless "Show Dangerous" is on.
transientboolNever saved to profiles (runtime-only). Text fields update as you type, before a row selection or action.
criticalbool | stringMark safety-critical; a string overrides the alert label.

Creating widgets

core.ui.checkbox(opts) -> element

Available on GMS and CW

Boolean toggle. Extra options: default (bool), disclosure (bool, default false). A disclosure expands the following controls that use its ID as visible_when; other checkboxes remain switches.

lua
local cb = core.ui.checkbox{ id = "enabled", label = "Enable", panel = "General", default = false }

core.ui.slider_int(opts) -> element

Available on GMS and CW

Integer slider. Extra options: default, min, max, step (defaults: 0 / 0 / 100 / 1).

lua
local spd = core.ui.slider_int{ id = "speed", label = "Speed", panel = "General",
                                default = 50, min = 0, max = 100, step = 5 }

core.ui.slider_float(opts) -> element

Available on GMS and CW

Float slider. Extra options: default, min, max, step, rounding (defaults: 0 / 0 / 1 / 0.1 / 2 decimals).

lua
local r = core.ui.slider_float{ id = "ratio", label = "Ratio", panel = "General",
                                default = 1.0, min = 0.0, max = 5.0, step = 0.1, rounding = 2 }

core.ui.dropdown(opts) -> element

Available on GMS and CW

Single-select dropdown. Extra options: options (array of strings), default (0-based index), table (optional searchable table presentation). Its value is always the original selected option index, including after searching or filtering.

lua
local target = core.ui.dropdown{ id = "target", label = "Target", panel = "Targeting",
                                 options = { "Closest", "Random" }, default = 0 }

For a searchable table, declare column labels and a separator used within each option. Panels, fields, and action buttons size and wrap automatically; scripts do not need spacing settings.

lua
local targets = core.ui.dropdown{
  id = "targets", label = "Targets", panel = "Targeting", transient = true,
  options = { "One;West;Ready", "Two;East;Paused" },
  table = {
    separator = ";", labelColumn = 1,
    columns = {
      { label = "Name" }, { label = "Region" },
      { label = "State", badges = { Ready = "success", Paused = "warning" } },
    },
    filters = { { label = "Available", column = 3, values = { "Ready" } } },
  },
}

table accepts:

KeyTypeNotes
separatorstringRequired, non-empty delimiter. Every option must have one field per column; fields are trimmed. Avoid the delimiter within a field.
columnstable[]Required, non-empty. Each column has a label; optional badges maps cell text to primary, success, warning, danger, info, or muted. Unmapped badge values use muted.
filterstable[]Optional buttons, each with label, a 1-based column, and allowed values (strings). An All button is provided automatically. Search also applies to filtered rows.
labelColumnint1-based column used in the selection summary; default 1.
imagetableOptional thumbnail on every row: kind ("minimap" or "item_icon") and a 1-based column whose cells hold the map or item ID. A row whose cell isn't a positive whole number shows a placeholder.
selectionLabelstringSelection marker and summary label; default Selected.
searchPlaceholderstringSearch hint; default Search options….
descriptionstringHelp text above the table.
emptyOptionstringWhen this is the sole option, display an empty table without a selectable row.
emptyTextstringEmpty-table message; default No options available.

Missing or inconsistent row data uses the ordinary dropdown. Updating options with :set_options() keeps the table description. Empty option lists show an empty table. Declaring table without column labels or a non-empty separator, or with an invalid image, raises an error.


core.ui.radio_group(opts) -> element

Available on GMS and CW

Like a dropdown but rendered as a row of buttons. Extra options: options, default (index). Value is the selected index.

lua
local mode = core.ui.radio_group{ id = "mode", label = "Mode", panel = "General",
                                  options = { "Safe", "Fast", "YOLO" }, default = 0 }

core.ui.multi_select(opts) -> element

Available on GMS and CW

Listbox where several options can be checked. Extra options: options, default (array of indices). Value is a sorted array of selected indices.

lua
local kinds = core.ui.multi_select{ id = "kinds", label = "Hit Kinds", panel = "Targeting",
                                    options = { "Mobs", "Reactors", "NPCs" }, default = { 0 } }

core.ui.input_text(opts) -> element

Available on GMS and CW

Text field. Extra options: default (string), flags (array of strings). Use flags = { "password" } to conceal the value and provide a Show/Hide button. Revealing does not change the setting; switching profile or session conceals it again and cancels pending edits.

lua
local note = core.ui.input_text{ id = "note", label = "Note", panel = "General", default = "" }

The password flag controls display only. The value is still saved unless transient = true.


core.ui.keybind(opts) -> element

Available on GMS and CW

Key capture. Extra option: default (key name string, e.g. "F8").

lua
local key = core.ui.keybind{ id = "toggle_key", label = "Toggle Key", panel = "General", default = "F8" }

core.ui.color(opts) -> element

Available on GMS and CW

RGBA color picker. Extra option: default (array of 4 floats { r, g, b, a }, each 0–1).

lua
local col = core.ui.color{ id = "tint", label = "Tint", panel = "General", default = { 1, 0, 0, 1 } }

core.ui.button(opts) -> element

Available on GMS and CW

One-shot action button — no value; poll it with :pressed() (see Buttons). Extra options: variant ("default", "destructive", "outline", "secondary", "ghost"), confirm (confirmation prompt shown before firing).

lua
local run = core.ui.button{ id = "run", label = "Delete", panel = "Actions", variant = "destructive", confirm = "Are you sure?" }

core.ui.static_text(opts) -> element

Available on GMS and CW

Display-only text (no value). Extra options: text (the content), style ("muted", "body", or "heading"), markdown (when true, render inline **bold**, *italic*, `code`). Update it later with :set_text().

lua
local status = core.ui.static_text{ id = "status", panel = "Actions", style = "body", text = "idle" }

core.ui.progress(opts) -> element

Available on GMS and CW

Read-only progress bar. Extra options: value, min, max (defaults 0 / 0 / 1), display ("percent" or "value"), color ("violet", "success", "warning", "danger", "info"). Drive it from script with :set_value().

lua
local bar = core.ui.progress{ id = "bar", label = "Progress", panel = "Actions", value = 0, min = 0, max = 100, color = "success" }

core.ui.image(opts) -> element

Available on GMS and CW

Display-only image from the game's own art (no value). Extra options: one source, either minimap (a map ID) or item_icon (an item ID); size ("small" 48 px square, "medium" 160 px tall, or "large" 288 px tall; medium and large span the panel; default "medium"); show_name (when true, show the map or item name under the image, or beside it at "small"). URLs and files are not accepted. The source is optional: without one the element shows a placeholder until you call :set_source(), and an ID the game has no image for shows "No image available" (at "small", a placeholder icon only). Passing both sources, an ID that isn't a positive whole number, or an unknown size raises an error.

lua
local preview = core.ui.image{ id = "preview", label = "Current target", panel = "Map Preview",
                               minimap = 100000000, show_name = true }

Pair it with a dropdown table's image column to pick from a list of maps. Build both from one list so the IDs can't drift apart; calling :set_source() with the image already shown does nothing, so it's safe every tick:

lua
local MAPS = { { 100000000, "Henesys" }, { 101000000, "Ellinia" }, { 102000000, "Perion" } }
local options = {}
for i, m in ipairs(MAPS) do options[i] = m[1] .. "|" .. m[2] end

local map = core.ui.dropdown{ id = "map", label = "Map", panel = "Map Preview", options = options,
  table = { separator = "|", labelColumn = 2, searchPlaceholder = "Search maps",
            columns = { { label = "Map ID" }, { label = "Name" } },
            image = { kind = "minimap", column = 1 } } }

function on_tick()
  preview:set_source{ minimap = MAPS[map:get() + 1][1] }
end

core.ui.separator(opts) -> element

Available on GMS and CW

Visual divider. With a label it renders as a small section heading; without one, a plain rule.

lua
core.ui.separator{ id = "sec1", label = "Advanced", panel = "General" }

Organizing your UI

Bigger scripts quickly outgrow one flat page. Three tools keep the menu tidy: custom tab icons, nav groups (submenus), and in-page sections.

core.ui.tab(opts)

Available on GMS and CW

Sets navigation metadata for one of your tabs. Call it once at the top of the script — order relative to element creation doesn't matter, but the tab only shows up once at least one element is placed on it. Returns nothing.

KeyTypeNotes
namestringRequired. The tab this metadata applies to (the same string you pass as tab).
iconstringSidebar icon — a kebab-case name from the icon set.
groupstringNav group label. Your tabs sharing the same group fold into one collapsible sidebar entry.
group_iconstringIcon for the group header. Among grouped tabs, the first one that sets it wins.
orderintSort order among script tabs / within the group (lower first).

Reserved tab names are rejected, plus the shared Lua tab (you can place elements there, but not restyle a surface every script shares). Groups only apply to script tabs — built-in tabs can't be regrouped or re-iconed.

lua
local GROUP = "Azura's Scripts"

core.ui.tab{ name = "AIO Grinding",     group = GROUP, group_icon = "crown", icon = "swords", order = 1 }
core.ui.tab{ name = "AIO Monster Park", group = GROUP, icon = "castle", order = 2 }
core.ui.tab{ name = "Secret Exploits",  group = GROUP, icon = "skull",  order = 3 }

core.ui.checkbox{ id = "enabled", label = "Enable", tab = "AIO Grinding", panel = "General", default = false }
-- ...

The sidebar now shows a single collapsible Azura's Scripts entry with the three tabs inside it, instead of three flat nav items.

In-page sections (tab strips)

Pass section on elements to split a busy page into sub-tabs, like the built-in Player page's Equip / Use / Set-up / Etc / Cash inventory tabs. Panels that share a section render under one tab strip; panels without a section stay pinned above the strip — put your master toggle there.

lua
local TAB = "AIO Grinding"

-- No section: always visible above the strip.
local enabled = core.ui.checkbox{ id = "enabled", label = "Enable", tab = TAB, panel = "General", default = false }

-- Buffs | Skills | Misc
core.ui.checkbox{ id = "haste", label = "Haste", tab = TAB, section = "Buffs", panel = "Party Buffs", default = true }
core.ui.checkbox{ id = "hs", label = "Holy Symbol", tab = TAB, section = "Buffs", panel = "Party Buffs", default = true }
core.ui.slider_int{ id = "combo", label = "Combo Count", tab = TAB, section = "Skills", panel = "Attacking",
                    default = 3, min = 1, max = 10 }
core.ui.checkbox{ id = "loot", label = "Auto Loot", tab = TAB, section = "Misc", panel = "Other", default = true }

Sections appear in the order they are first used. Keep every element of one panel in the same section, and move elements later with elem:set_section(). Sections are purely visual — IDs, values, and saved profiles are unaffected.

Icon set

icon and group_icon accept these kebab-case names (a curated subset of lucide.dev). Unknown names quietly fall back to the default icon, so typos never break a menu.

activity, alarm-clock, anchor, archive, award, axe, backpack, banknote, bell, bird, bomb, book, book-open, bot, braces, brain, brush, bug, calendar, camera, car, castle, circle-dollar-sign, clock, cloud-lightning, code-2, cog, coins, compass, cpu, crosshair, crown, database, diamond, dices, dna, droplet, egg, eye, eye-off, feather, file-code, filter, fish, flag, flame, flask-conical, footprints, gamepad-2, gauge, gem, ghost, gift, glasses, hammer, hash, heart, hourglass, house, infinity, key, landmark, layers, layout-grid, leaf, link, list, lock, magnet, mail, map, map-pin, medal, message-square, moon, mountain, music, navigation, package, palette, pause, paw-print, pen, percent, pickaxe, pin, plane, play, puzzle, rabbit, refresh-cw, repeat, rocket, route, ruler, scale, scissors, scroll, scroll-text, search, send, settings, shield, shield-check, ship, shopping-cart, skull, sliders-horizontal, snowflake, sparkles, sprout, star, store, sun, sword, swords, table, target, tent, terminal, test-tube, timer, trending-up, trees, trophy, truck, umbrella, unlock, user, users, wallet, wand-2, watch, waves, wheat, wrench, zap

The element handle

Every constructor returns a handle. A handle stays safe even if the element is later removed (calls become no-ops). Mutator methods return the handle, so they can be chained.

element:get() -> any

Available on GMS and CW

Returns the current value (bool / number / string / array, depending on widget type).

lua
if cb:get() then core.log("on") end

element:set(value) -> boolean

Available on GMS and CW

Sets the value, coerced to the element's type, and pushes it to the web menu.

lua
spd:set(75)

element:id() -> string

Available on GMS and CW

Returns the full namespaced ID (e.g. lua.my_script.enabled).


element:set_label(label) -> element

Available on GMS and CW

Changes the display label at runtime.


element:set_tooltip(text) -> element

Available on GMS and CW

Changes the tooltip at runtime.


element:set_visible(visible) -> element

Available on GMS and CW

Shows or hides the element. Hidden elements are removed from the layout.

lua
spd:set_visible(mode:get_index() ~= 2)

element:visible() -> boolean

Available on GMS and CW

Returns whether the element is currently visible.


element:set_enabled(enabled) -> element

Available on GMS and CW

Enables or disables interaction (the element stays visible but greys out).


element:enabled() -> boolean

Available on GMS and CW

Returns whether the element is currently enabled.


element:set_visible_when(other_id) -> element

Available on GMS and CW

Binds conditional visibility to another element's value at runtime.


element:set_order(order) -> element

Available on GMS and CW

Changes the sort order within the panel.


element:set_panel(panel) -> element

Available on GMS and CW

Moves the element to a different panel.


element:set_section(section) -> element

Available on GMS and CW

Moves the element to a different in-page section (see Organizing your UI). Pass "" to detach it from the strip and pin it above.


element:set_options(options) -> element

Available on GMS and CW

Replaces the option list of a dropdown, radio_group, or multi_select at runtime. The selected index is clamped to the new range.

lua
target:set_options({ "Closest", "Random", "Boss", "Elite" })

element:get_index() -> number

Available on GMS and CW

Returns the selected index of a dropdown or radio_group.


element:set_index(index) -> element

Available on GMS and CW

Sets the selected index of a dropdown or radio_group.


element:get_selected() -> any

Available on GMS and CW

For dropdown/radio_group returns the selected option string; for multi_select returns the array of selected indices.


element:set_selected(indices) -> element

Available on GMS and CW

Sets the selected indices of a multi_select (array of indices).


element:set_min(value) -> element

Available on GMS and CW

Changes a slider's minimum at runtime (value is re-clamped).


element:set_max(value) -> element

Available on GMS and CW

Changes a slider's maximum at runtime (value is re-clamped).


element:set_step(value) -> element

Available on GMS and CW

Changes a slider's step at runtime.


element:set_text(text) -> element

Available on GMS and CW

Changes the text of a static_text element.


element:set_value(value) -> element

Available on GMS and CW

Sets the current value of a progress bar.


element:set_source(source) -> element

Available on GMS and CW

Changes the picture of an image element: { minimap = id } or { item_icon = id }. Pass nil to clear it; a table with neither key raises an error.


element:source() -> table | nil

Available on GMS and CW

Returns the current source of an image element as { minimap = id } or { item_icon = id }, or nil when none is set.


element:pressed() -> boolean

Available on GMS and CW

For a button: returns true exactly once after each click, then clears. Poll it in on_tick.


element:remove()

Available on GMS and CW

Removes the element from the menu.

Buttons

There is no UI event callback system; buttons are polled. :pressed() returns true once per click and then resets, so check it inside on_tick:

lua
local run = core.ui.button{ id = "run", label = "Run Once", panel = "Actions" }

function on_tick()
  if run:pressed() then
    core.log("button clicked")
  end
end

Widget reference

ConstructorValue (:get())Extra optionsKey methods
checkboxbooldefault:get :set
slider_intnumberdefault min max step:get :set :set_min :set_max :set_step
slider_floatnumberdefault min max step roundingsame as slider_int
dropdownindexoptions default:get_index :set_index :get_selected :set_options
radio_groupindexoptions defaultsame as dropdown
multi_selectindex arrayoptions default:get_selected :set_selected :set_options
input_textstringdefault flags:get :set
keybindstringdefault:get :set
color{r,g,b,a}default:get :set
button—variant confirm:pressed
static_text—text style markdown:set_text
progressnumbervalue min max display color:set_value
image—minimap item_icon size show_name:set_source :source
separator———

Full example

lua
plugin = {
    name = "UI Demo",
    version = "1.0.0",
    author = "Author",
    description = "Showcases the core.ui API",
    load = true
}

local TAB = "UI Demo"

local enabled = core.ui.checkbox{ id = "enabled", label = "Enable", tab = TAB, panel = "General", default = true }
local mode    = core.ui.radio_group{ id = "mode", label = "Mode", tab = TAB, panel = "General",
                                     options = { "Safe", "Fast", "YOLO" }, default = 0 }
local speed   = core.ui.slider_int{ id = "speed", label = "Speed", tab = TAB, panel = "General",
                                    default = 50, min = 0, max = 100, step = 5 }
local target  = core.ui.dropdown{ id = "target", label = "Target", tab = TAB, panel = "Targeting",
                                  options = { "Closest", "Random" }, default = 0 }
local status  = core.ui.static_text{ id = "status", tab = TAB, panel = "Actions", style = "body", text = "idle" }
local bar     = core.ui.progress{ id = "bar", label = "Progress", tab = TAB, panel = "Actions", value = 0, min = 0, max = 100 }
local run     = core.ui.button{ id = "run", label = "Run Once", tab = TAB, panel = "Actions" }

local running, ticks = false, 0

function on_tick()
    if run:pressed() and enabled:get() then
        running, ticks = true, 0
        status:set_text("running (mode=" .. mode:get_selected() .. ", target=" .. target:get_selected() .. ")")
        target:set_options({ "Closest", "Random", "Boss", "Elite" })  -- grow the list at runtime
    end

    if running then
        ticks = ticks + 1
        bar:set_value(math.min(ticks, 100))
        if ticks >= 100 then
            running = false
            status:set_text("done")
        end
    end

    speed:set_visible(mode:get_index() ~= 2)  -- hide Speed in YOLO mode
end

Legacy: core.menu

The original core.menu functions still work — they are thin wrappers over core.ui that force the Lua tab and the legacy lua.<id> naming (no per-script segment). They return a boolean instead of a handle, so to read/write their values you use core.get_setting / core.set_setting with the full lua.<id> path. Prefer core.ui for new scripts.

core.menu.create_checkbox(id, label, tab, panel, default_value [, tooltip]) -> boolean

Available on GMS and CW

Creates a checkbox setting (the tab argument is ignored — always "Lua").

lua
core.menu.create_checkbox("myScript.enabled", "Enable Feature", "Lua", "My Script", false)
-- Registered as id="lua.myScript.enabled"; read with core.get_setting("lua.myScript.enabled")

core.menu.create_slider_int(id, label, tab, panel, default, min, max [, step [, tooltip]]) -> boolean

Available on GMS and CW

Creates an integer slider setting. step defaults to 1.


core.menu.create_slider_float(id, label, tab, panel, default, min, max [, step [, tooltip [, rounding]]]) -> boolean

Available on GMS and CW

Creates a float slider setting. step defaults to 0.1, rounding defaults to 2.


core.menu.create_dropdown(id, label, tab, panel, options, default_index [, tooltip]) -> boolean

Available on GMS and CW

Creates a dropdown setting. options is a Lua array of strings; default_index is 0-based.


core.menu.create_keybind(id, label, tab, panel [, default_key [, tooltip]]) -> boolean

Available on GMS and CW

Creates a keybind setting.


core.menu.create_input_text(id, label, tab, panel [, default_value [, tooltip]]) -> boolean

Available on GMS and CW

Creates a text input setting.