Skip to main content

UI

UI is the high-level client API for building interactive interface trees from Lua.

Typical use:

  • server ships a resource with client.lua
  • client script creates UI with UI.*
  • gameplay actions go back to server through Event/Command/service APIs

Error Handling Pattern​

  • Success: err == nil
  • Failure: err is an error code string

Scope Model​

  • UI is client-only.
  • Server does not directly render UI.
  • Server influences UI by shipping client resources and by sending events/data to clients.
  • Runtime may render engine-owned overlay UI that is outside Lua ownership (for example bottom-right mtadm vX.Y.Z watermark).
  • Pointer visibility is managed separately through Cursor.
  • World-space overlay lines/wireframes are handled by Visual, not by UI.

This page documents the currently implemented UI runtime.
If a control or event is not listed here, scripts should not depend on it.

Root Model (LuaUI vs NodeUI)​

  • LuaUI is the engine/internal CanvasLayer root (implementation detail).
  • NodeUI is the logical Lua API domain for UI graph ownership.
  • Public scripts should use UI.* (not raw internal engine paths).

Resource ownership:

  • each resource writes only to its own UI subtree.
  • canonical logical root: /NodeUI/resources/{resourceName}/...
  • resource stop/restart automatically removes owned UI subtree.
  • resourceName is resolved from the executing Lua script resource context.

Runtime Model​

UI is a retained-tree system.

That means:

  • UI.Create(...) creates persistent controls owned by the resource.
  • UI.SetProps(...) updates existing controls in place.
  • UI.ClearResource(...) destroys the full resource subtree.

Production guidance:

  • do not UI.ClearResource(...) on every small state change
  • prefer creating a stable root once, then patch child props in place
  • for overlays/panels, prefer visible = true/false over destroy/recreate
  • rebuild only when structure changes materially (for example a different tab set or a different row count that cannot be patched)

This matters for performance:

  • creating hundreds of controls in one frame is expensive
  • clearing and recreating a large tree on every open/close or every metric update is not a good production pattern
  • the intended fast path is retained controls plus in-place updates

Window/Dialog API​

The runtime now exposes built-in window and dialog kinds.

Goals:

  • reduce repeated Lua boilerplate for top-level shells
  • keep title/close/drag behavior consistent across resources
  • keep child composition on the existing UI parent model

Current first-version scope:

  • no automatic focus/capture behavior here yet
  • no modal-stack redesign yet
  • no built-in window persistence/state saving

Semantics:

window

  • movable editor/tool window shell
  • intended for inspectors, browser panes, settings, hierarchy panes
  • children parented to the window id are inserted into the window content area automatically

dialog

  • centered dialog shell
  • intended for confirm/open/save/picker dialogs
  • children parented to the dialog id are inserted into the dialog content area automatically

Close behavior:

  • closeMode = "hide":
    • built-in close button hides the existing window/dialog instance
    • moved position is preserved because the same shell stays alive
    • close_requested is still emitted
  • closeMode = "destroy":
    • built-in close button only emits close_requested
    • the resource is responsible for removing/recreating the UI element if it wants destroy-on-close behavior

Function List​

FunctionDescriptionScope
UI.GetViewportSizeRead the current client viewport size for layout.C
UI.GetCountryInfoResolve a country code into UI-ready country display data.C
UI.GetMetricsInspect resolved runtime metrics for an existing UI element.C
UI.CreateCreate one UI element.C
UI.SetParentReparent one UI element.C
UI.SetPropsPatch UI element properties.C
UI.TreeSelectApply native selection to a tree_view.C
UI.TreeClearSelectionClear native selection on a tree_view.C
UI.GetRead one UI element.C
UI.ListList child UI elements.C
UI.RemoveRemove one UI element.C
UI.BindBind UI interaction callback.C
UI.UnbindRemove one UI callback binding.C
UI.ClearResourceRemove all UI owned by a resource.C

Event List​

EventDescriptionScope
ui:createdFired after a UI element is created.C
ui:updatedFired after a UI element is updated.C
ui:removedFired after a UI element is removed.C
ui:actionFired after a bound UI interaction callback executes.C

Element Kinds (v1)​

  • container
  • window
  • dialog
  • label
  • line
  • circle
  • rich_text
  • image
  • button
  • menu_button
  • check_button
  • color_button
  • input
  • slider
  • stack
  • scroll_container
  • tab_container
  • item_list
  • table_view
  • tree_view

tab_container includes Godot's built-in tab header bar.
There is currently no separate tab_bar kind in Lua UI API.

Current runtime note:

  • stack, scroll_container, input, and slider are implemented.
  • table_view is intended for high-density lists such as scoreboards or admin lists.
  • tree_view is intended for hierarchy editing (parent/child UI).
  • While a native input has focus, Lua input binds/hotkeys and the core chat-open action are suppressed.
  • If a Lua UI should be mouse- or pointer-interactive, the resource should also acquire a cursor handle through Cursor.Acquire(...).

Common Properties​

  • name (string)
  • visible (boolean)
  • x, y, width, height (number, pixels)
  • mouseFilter (ignore|pass|stop, for container)
  • draggable (boolean, for container)
  • title (string, for window/dialog)
  • closable (boolean, for window/dialog)
  • closeMode (hide|destroy, for window/dialog; default hide)
  • centered (boolean, for dialog)
  • showHeader (boolean, for window)
  • draggable (boolean, for window; default true)
  • cornerRadius (number, for container/window/dialog/button/menu_button)
  • contentPadding (number, for button/menu_button; applies equal inner padding on all sides)
  • text (string, for label/button/menu_button/check_button)
  • disabled (boolean, for button/menu_button/check_button/color_button)
  • backgroundColor (color table, for button/menu_button)
  • iconSource (string, for button/menu_button; same source rules as image.source)
  • tooltip (string, for button/menu_button/color_button)
  • focusable (boolean, for button/menu_button/tree_view; default true)
  • x1, y1, x2, y2 (number, for line)
  • thickness (number, for line/circle)
  • x, y, radius (number, for circle; center-positioned)
  • filled (boolean, for circle)
  • text (string, for rich_text; BBCode-enabled)
  • source (string, for image; supports res://..., user://..., or distributed resource assets via resourceName:path/to/file.svg)
  • stretchMode (scale|tile|keep_aspect|keep_aspect_centered|keep_aspect_covered, for image)
  • placeholder (string, for input)
  • checked (boolean, for check_button)
  • value (color table { r, g, b, a }, for color_button)
  • editable (boolean, for input)
  • secret (boolean, for input)
  • value (number, for slider)
  • min (number, for slider)
  • max (number, for slider)
  • step (number, for slider)
  • editable (boolean, for slider)
  • wrap (boolean, for label)
  • clipText (boolean, for label)
  • autoresizeHeight (boolean, for label)
  • horizontalAlignment (left|center|right|fill, for label)
  • verticalAlignment (top|center|bottom|fill, for label)
  • direction (vertical|horizontal, for stack)
  • gap (number, for stack)
  • contentWidth (number, for scroll_container)
  • contentHeight (number, for scroll_container)
  • scrollX (number, for scroll_container)
  • scrollY (number, for scroll_container)
  • value (string/number/bool, kind-specific)
  • tabs (table<string>, for tab_container)
  • selectedIndex (number, 0-based, for tab_container)
  • items (table<string>, for item_list)
  • selectedIndex (number, 0-based, for item_list)
  • items (table<table>, for menu_button)
  • columns (table<string>, for table_view)
  • showHeaders (boolean, optional, default true, for table_view)
  • columnWidths (table<number>, optional, for table_view)
  • columnExpandRatios (table<number>, optional, for table_view)
  • columnAlignments (table<string>, optional, for table_view)
  • rowHeight (number, optional, for table_view)
  • rows (table<table>, for table_view)
  • selectedIndex (number, 0-based, for table_view)
  • nodes (table<table>, for tree_view)
  • showHeaders (boolean, optional, default true, for tree_view)
  • hideRoot (boolean, optional, default true, for tree_view)
  • allowReparent (boolean, optional, default false, for tree_view)
  • focusable (boolean, optional, default true, for tree_view)
  • selectedIndex (number, 0-based, for tree_view)
  • selectedNodeId (string, optional, for tree_view)

Layout semantics:

  • when options.parent is set, x/y/width/height are interpreted in the parent control's local space.
  • when no parent is set, layout is relative to the resource UI root.
  • children under stack are laid out automatically by the stack direction/gap rules.
  • children under scroll_container render inside a clipped scrollable content area.
  • scroll_container can declare virtual content size through contentWidth / contentHeight.
  • if contentWidth / contentHeight are omitted or <= 0, the runtime auto-measures content bounds from the actual child controls.
  • scroll_container can be positioned programmatically through scrollX / scrollY.

Current layout limitations:

  • layout is absolute-position based
  • there is no padding/margin API on stack yet
  • there is no public ellipsis mode yet
  • autoresizeHeight helps labels grow to fit wrapped content, but only for labels

Performance guidance:

  • prefer a persistent root panel that is shown/hidden
  • prefer updating row text/images in place over rebuilding full lists
  • prefer lazy creation of expensive subtrees such as tabs or diagnostics panes
  • avoid large full-tree rebuilds in high-frequency event paths
  • for very large lists, prefer virtualization or a bounded row pool instead of one control per logical row
  • prefer scroll virtualization for long tables instead of one control per logical row
  • prefer table_view over many generic controls when the UI is fundamentally a live table
  • prefer tree_view when the UI needs hierarchy + drag/drop reparent semantics

Drag guidance:

  • draggable = true is intended for retained containers such as windows/panels
  • dragging updates the live control position immediately
  • if a resource wants drag position to survive a later rebuild, it should store the last drag_end coordinates and reuse them on the next render
  • a visible top-level draggable container is treated as an interactive foreground window and automatically suspends gameplay input while it is open

Resource asset note:

  • if a Lua resource wants to use its own images, the files must be declared in the resource meta.json assets[] list
  • once the resource is started, those assets are distributed to clients like other resource packets
  • image.source = "resourceName:path/to/file.svg" resolves against the distributed asset payload for that resource
  • button.iconSource = "resourceName:path/to/file.png" resolves the same way
  • menu_button.iconSource = "resourceName:path/to/file.svg" resolves the same way

menu_button.items entries support:

  • text (string)
  • id (string)
  • disabled (boolean)
  • separator (boolean)

menu_button uses Godot's built-in popup menu behavior. When an item is picked, item_selected dispatches a payload table with:

  • index (number)
  • text (string)
  • id (string)

color_button uses Godot's native ColorPickerButton. When the user changes the color, color_changed dispatches a payload table with:

  • r (number)
  • g (number)
  • b (number)
  • a (number)

Example:

local pickerId, err = UI.Create("color_button", {
name = "marker_color",
x = 16,
y = 40,
width = 220,
height = 32,
value = { r = 1.0, g = 0.5, b = 0.0, a = 0.85 }
})

if not err then
UI.Bind(pickerId, "color_changed", function(ctx)
local value = type(ctx.value) == "table" and ctx.value or ctx
Logger.Info(string.format(
"picked color r=%.2f g=%.2f b=%.2f a=%.2f",
tonumber(value.r) or 0,
tonumber(value.g) or 0,
tonumber(value.b) or 0,
tonumber(value.a) or 0
))
end)
end

Functions​

UI.GetViewportSize​

C Client Only

local viewport, err = UI.GetViewportSize()

Returns:

  • viewport (table | nil)
  • err (nil | string)

Viewport table fields:

  • width (number)
  • height (number)

Example:

local viewport, err = UI.GetViewportSize()
if err then
Logger.Error("UI.GetViewportSize failed: " .. tostring(err))
return
end

local maxPanelBottom = math.floor(viewport.height * 0.80)
local panelY = 120
local panelHeight = math.max(120, maxPanelBottom - panelY)

UI.GetCountryInfo​

C Client Only

local info, err = UI.GetCountryInfo(countryCode)

Parameters:

  • countryCode (string): two-letter country code such as "de" or "us". Unknown values resolve to the fallback country entry.

Returns:

  • info (table | nil)
  • err (nil | string)

Country info table fields:

  • code (string)
  • name (string)
  • flagIcon (string): UI-ready texture resource path for use with image.source

Example:

local info, err = UI.GetCountryInfo(player.countryCode or "xx")
if not err and info then
UI.Create("image", {
name = "flag",
x = 8,
y = 8,
width = 20,
height = 14,
source = info.flagIcon
}, { parent = rowId })
end

UI.GetMetrics​

C Client Only

local metrics, err = UI.GetMetrics(uiId)

Returns resolved runtime metrics for an existing UI element.

Returns:

  • metrics (table | nil)
  • err (nil | string)

Common fields:

  • width
  • height
  • visible
  • visibleInTree

table_view currently adds:

  • contentHeight
  • contentWidth
  • headerHeight
  • totalContentHeight
  • headersVisible
  • scrollBarVisible
  • scrollBarWidth

UI.Create​

C Client Only

local uiId, err = UI.Create(kind, props, options)

Parameters:

  • kind (string): element kind.
  • props (table): initial element props.
  • options (table, optional):
  • parent (string): parent uiId (default resource root)
  • name (string)
  • tabIndex (number, optional): when parent is a tab_container, choose which tab page receives this element (0-based).
    If omitted, current tab is used.

Default parent resolution:

  • when options.parent is omitted, parent defaults to /NodeUI/resources/{currentResource} root.

Returns:

  • uiId (string | nil)
  • err (nil | string)

Label example:

local labelId, err = UI.Create("label", {
name = "server_status",
x = 16,
y = 16,
width = 320,
height = 48,
text = "This text stays inside the assigned rectangle.",
wrap = true,
clipText = true,
autoresizeHeight = true,
horizontalAlignment = "left",
verticalAlignment = "top",
visible = true
})

Rich text example:

local nameId, err = UI.Create("rich_text", {
name = "colored_name",
x = 16,
y = 72,
width = 320,
height = 24,
text = "Don[color=#ff0000]iel&lt;3[/color]",
wrap = false,
visible = true
})

Image example:

local info, err = UI.GetCountryInfo("de")
if not err and info then
UI.Create("image", {
name = "flag_de",
x = 16,
y = 104,
width = 24,
height = 16,
source = info.flagIcon,
stretchMode = "keep_aspect_centered",
visible = true
})
end

Stack example:

local actionsId, err = UI.Create("stack", {
name = "actions",
x = 16,
y = 72,
width = 260,
height = 160,
direction = "vertical",
gap = 8,
visible = true
})

UI.Create("button", {
name = "kick_btn",
width = 260,
height = 36,
text = "Kick"
}, { parent = actionsId })

UI.Create("button", {
name = "ban_btn",
width = 260,
height = 36,
text = "Ban"
}, { parent = actionsId })

Scroll container example:

local scrollId, err = UI.Create("scroll_container", {
name = "details_scroll",
x = 320,
y = 16,
width = 420,
height = 300,
visible = true,
contentHeight = 1200,
scrollY = 0
})

UI.Create("label", {
name = "details_text",
x = 0,
y = 0,
width = 400,
height = 240,
text = "Long content...",
wrap = true,
clipText = true,
visible = true
}, { parent = scrollId })

UI.Bind(scrollId, "scroll_changed", function(ctx)
local offsetY = ctx.value and ctx.value.y or 0
Logger.Info("Scroll offset Y: " .. tostring(offsetY))
end)

Window example:

local windowId, err = UI.Create("window", {
name = "inspector_window",
title = "Inspector",
x = 1320,
y = 120,
width = 360,
height = 640,
visible = true,
closable = true,
draggable = true,
cornerRadius = 8
})

UI.Create("label", {
name = "inspector_title",
x = 16,
y = 16,
width = 220,
height = 24,
text = "Object Inspector",
visible = true
}, { parent = windowId })

UI.Bind(windowId, "close_requested", function()
UI.SetProps(windowId, { visible = false })
end)

Dialog example:

local dialogId, err = UI.Create("dialog", {
name = "confirm_delete",
title = "Delete Map",
width = 420,
height = 220,
visible = true,
closable = true,
centered = true,
cornerRadius = 8
})

UI.Create("label", {
name = "confirm_text",
x = 16,
y = 16,
width = 260,
height = 24,
text = "Delete this map?",
visible = true
}, { parent = dialogId })

Table view example:

local tableId, err = UI.Create("table_view", {
name = "scoreboard_table",
x = 24,
y = 120,
width = 952,
height = 520,
visible = true,
columns = { "ID", "Name", "Country", "Ping", "FPS" },
showHeaders = true,
columnWidths = { 64, 0, 120, 80, 80 },
columnExpandRatios = { 0, 5, 2, 0, 0 },
columnAlignments = { "left", "left", "right", "right", "right" },
rowHeight = 26,
rows = {
{
cells = {
{ text = "1" },
{ text = "Don#ff0000iel&lt;3", parseColorCodes = true },
{ text = "AT", icon = "res://Game/Assets/Icons/flag-icons-main/flags/4x3/at.svg", iconMaxWidth = 20, horizontalAlignment = "right", iconAlignment = "right" },
{ text = "21", horizontalAlignment = "right" },
{ text = "144", horizontalAlignment = "right" }
}
}
},
selectedIndex = 0
})

UI.Bind(tableId, "item_selected", function(ctx)
local rowIndex = ctx.value or -1
Logger.Info("Selected row: " .. tostring(rowIndex))
end)

table_view row model:

  • each row is a table with cells
  • each cells[i] entry may contain:
  • text (string)
  • icon (string texture path, optional)
  • iconMaxWidth (number, optional)
  • horizontalAlignment (left|center|right|fill, optional)
  • iconAlignment (left|center|right, optional)
  • parseColorCodes (boolean, optional; renders inline #RRGGBB segments like chat)

Current table_view limits:

  • rendered by one native table control for predictable sizing and low per-row overhead
  • built for dense tabular data, not rich text paragraphs
  • cells support plain text plus optional icon
  • inline color-code rendering is supported for short label-style cells via parseColorCodes = true

Tree view example:

local treeId, err = UI.Create("tree_view", {
name = "elements_tree",
x = 20,
y = 90,
width = 520,
height = 360,
columns = { "Name", "Type", "ID" },
showHeaders = true,
hideRoot = true,
allowReparent = true,
nodes = {
{ id = "root_empty", parentId = "", cells = { "[E] Root", "EMPTY", "root_empty" } },
{ id = "cube_1", parentId = "root_empty", cells = { "[C] Cube 1", "CUBE", "cube_1" } },
{ id = "sphere_1", parentId = "root_empty", cells = { "[S] Sphere 1", "SPHERE", "sphere_1" } }
},
selectedNodeId = "cube_1"
})

UI.Bind(treeId, "item_selected", function(ctx)
local rowIndex = tonumber(ctx.value) or -1
Logger.Info("Tree selected row index: " .. tostring(rowIndex))
end)

UI.Bind(treeId, "item_activated", function(ctx)
local rowIndex = tonumber(ctx.value) or -1
Logger.Info("Tree activated row index: " .. tostring(rowIndex))
end)

UI.Bind(treeId, "item_reparented", function(ctx)
local payload = ctx.value or {}
-- payload.sourceId
-- payload.targetId
-- payload.parentId
-- payload.dropSection (-1,0,1)
end)

Input example:

local searchId, err = UI.Create("input", {
name = "search_box",
x = 16,
y = 16,
width = 280,
height = 34,
text = "",
placeholder = "Filter players...",
editable = true,
secret = false,
visible = true
})

UI.SetParent​

C Client Only

local err = UI.SetParent(uiId, parentId, options)

Parameters:

  • uiId (string)
  • parentId (string)
  • options (table, optional)

Returns:

  • err (nil | string)

UI.SetProps​

C Client Only

local err = UI.SetProps(uiId, patch, options)

Parameters:

  • uiId (string)
  • patch (table): partial property update.
  • options (table, optional)

Returns:

  • err (nil | string)

UI.Get​

C Client Only

local element, err = UI.Get(uiId)

Returns:

  • element (table | nil)
  • err (nil | string)

UI.TreeSelect​

C Client Only

local err = UI.TreeSelect(uiId, nodeIds, selectedNodeId, options)

Parameters:

  • uiId (string): target tree_view id
  • nodeIds (table<string>): selected node ids
  • selectedNodeId (string, optional): focused/current node id
  • options (table, optional)
  • resource (string, optional): explicit resource scope override. default is current script resource.

Returns:

  • err (nil | string)

Notes:

  • uses native tree selection directly
  • does not rebuild the tree structure

UI.TreeClearSelection​

C Client Only

local err = UI.TreeClearSelection(uiId, options)

Parameters:

  • uiId (string): target tree_view id
  • options (table, optional)
  • resource (string, optional): explicit resource scope override. default is current script resource.

Returns:

  • err (nil | string)

Notes:

  • uses native tree deselection directly
  • does not rebuild the tree structure

UI.List​

C Client Only

local elements, err = UI.List(parentId, options)

Parameters:

  • parentId (string, optional): default resource root.
  • options (table, optional):
  • recursive (boolean, default false)
  • kind (string or table)
  • limit (number)

Returns:

  • elements (table | nil)
  • err (nil | string)

UI.Remove​

C Client Only

local err = UI.Remove(uiId, options)

Parameters:

  • uiId (string)
  • options (table, optional):
  • recursive (boolean, default true)

Returns:

  • err (nil | string)

UI.Bind​

C Client Only

local bindId, err = UI.Bind(uiId, eventName, handler, options)

Parameters:

  • uiId (string)
  • eventName (string): clicked, pressed, toggled, hover_enter, hover_exit, drag_start, drag, drag_end, close_requested, text_changed, focus, blur, tab_changed, item_selected, item_activated, item_reparented, scroll_changed
  • handler (function)
  • options (table, optional):
  • resource (string, optional): explicit resource scope override. default is current script resource.

Handler signature:

function handler(ctx)
-- ctx.uiId
-- ctx.eventName
-- ctx.value (for tab_changed/item_selected/item_activated: selectedIndex as number)
-- (for menu_button item_selected: { index, text, id, ... })
-- (for item_reparented: { sourceId, targetId, parentId, dropSection })
-- (for drag_*: { x, y, mouseX, mouseY, deltaX, deltaY })
-- (for scroll_changed: { x, y })
-- ctx.timestamp
end

Current event support:

  • clicked / pressed / hover_enter / hover_exit / drag_start / drag / drag_end: container
  • close_requested: window, dialog
  • drag_end: window
  • clicked / pressed: button
  • item_selected: menu_button
  • toggled: check_button
  • color_changed: color_button
  • focus
  • blur
  • text_changed: input
  • value_changed: slider
  • tab_changed: tab_container
  • item_selected: item_list, table_view, tree_view
  • item_activated: item_list, table_view, tree_view
  • item_reparented: tree_view
  • scroll_changed: scroll_container

text_changed dispatches in both cases:

  • when the user edits a native input
  • when script code changes text or value through UI.SetProps

Not implemented yet:

  • released

Returns:

  • bindId (string | nil)
  • err (nil | string)

UI.Unbind​

C Client Only

local err = UI.Unbind(bindId)

Returns:

  • err (nil | string)

UI.ClearResource​

C Client Only

local err = UI.ClearResource(resourceName)

Parameters:

  • resourceName (string, optional): default current resource.

Default resource resolution:

  • when resourceName is omitted, current script resource is used.

Returns:

  • err (nil | string)

Events​

All events use the standard event envelope:

  • event.name
  • event.payload
  • event.source
  • event.target
  • event.timestamp
  • event.correlationId
  • event.version

ui:created (Event Bus)​

Payload:

  • uiId, kind, parentId, resource, props

ui:updated (Event Bus)​

Payload:

  • uiId, patch, resource

ui:removed (Event Bus)​

Payload:

  • uiId, resource

ui:action (Event Bus)​

Payload:

  • uiId, eventName, value, resource

Security and Authority Notes​

  • UI is client-local and non-authoritative.
  • UI callbacks must not be treated as trusted gameplay authority.
  • To change server-authoritative state, call server-validated APIs (Command, Event, Chat, Node.RequestSim, etc.).

Error Codes​

  • ERR_NOT_FOUND
  • ERR_INVALID
  • ERR_INVALID_KIND
  • ERR_INVALID_PARENT
  • ERR_INVALID_HANDLER
  • ERR_PERMISSION_DENIED
  • ERR_RESOURCE_SCOPE
  • ERR_BUSY

Quick Examples​

Create container + label + button:

local panelId, err = UI.Create("container", {
name = "mainPanel",
x = 32, y = 32, width = 420, height = 180
})

local labelId = UI.Create("label", {
text = "Race Control",
x = 16, y = 16, width = 200, height = 24
}, { parent = panelId })

local btnId = UI.Create("button", {
text = "Start Race",
x = 16, y = 64, width = 140, height = 36
}, { parent = panelId })

Bind button click and request server action:

UI.Bind(btnId, "clicked", function(ctx)
Command.Execute("/race start", { sourceType = "chat" })
end)

Use default Godot-style tab headers with tab_container:

local tabsId = UI.Create("tab_container", {
x = 32, y = 240, width = 520, height = 46,
tabs = { "Players", "Resources", "Server", "Bans" },
selectedIndex = 0
})

UI.Bind(tabsId, "tab_changed", function(ctx)
local selectedIndex = tonumber(ctx.value) or 0
print("Selected tab index: " .. tostring(selectedIndex))
end)

Place content under specific tabs:

local playersPanel = UI.Create("container", {
x = 0, y = 0, width = 900, height = 500
}, {
parent = tabsId,
tabIndex = 0 -- Players
})

local resourcesPanel = UI.Create("container", {
x = 0, y = 0, width = 900, height = 500
}, {
parent = tabsId,
tabIndex = 1 -- Resources
})

Use a scrollable player list with item_list:

local listId = UI.Create("item_list", {
x = 32, y = 300, width = 360, height = 260,
items = { "#1 PlayerOne", "#2 PlayerTwo", "#3 PlayerThree" },
selectedIndex = 0
})

UI.Bind(listId, "item_selected", function(ctx)
local rowIndex = tonumber(ctx.value) or 0
print("Selected player row: " .. tostring(rowIndex))
end)