Elysium scripting Editor API index
Build

Menus & controls

Groups, tabs, floating windows and every kind of control - plus custom controls you draw yourself.

A script puts its settings in the menu, in the right half of its page in the Scripts tab - or in a floating window of its own. Controls are objects: the call that makes one returns it, and from then on you ask it for its value whenever you need it.

local group = ui.group( "aim helper" )

local enabled  = group:toggle( "enabled", true )
local distance = group:slider( "max distance", 100, 3000, 1200, { step = 50, format = "%d u" } )
local mode     = group:choice( "mode", { "closest", "lowest health", "crosshair" } )

on( "move", function( cmd )
	if not enabled:get( ) then
		return
	end

	local limit  = distance:get( )     -- a number
	local picked = mode:value( )       -- "closest", "lowest health" or "crosshair"
end )

You rarely wait for a callback. The usual pattern is the one above: build the controls once at load, keep them in locals, read them in handlers. The values are saved for you - see Saving.

#Groups, tabs and windows

Controls are made by a group: group:toggle( ... ), group:slider( ... ). A group is a titled block. Where groups appear depends on how they were made.

#ui.group( title ) → group

A titled block of controls in the script's page. If the script has not made a tab, the group goes into a tab the runtime makes for it, named like the script; otherwise into the newest tab.

#ui.tab( title ) → tab

A page of groups. A script with several tabs shows a row of buttons above its controls to switch between them. Groups made with ui.group afterwards go into the newest tab.

#t:group( title ) → group

A group inside a particular tab.

local general = ui.tab( "general" )
local looks   = ui.tab( "looks" )

local timing = general:group( "timing" )
local colours = looks:group( "colours" )

#ui.window{ options } → window

A floating window, drawn over the menu. It is moved by its title strip, keeps the position you drag it to while the script runs, and belongs to the script - when the script unloads it goes too. options is a table:

FieldMeaningDefault
titleThe title shown in the window."window"
x, yWhere it first appears, in pixels from the top left of the screen.120, 120
w, hIts size. At least 160 by 80.280, 240
resizableWhether the user can resize it - down to 200 by 120 at the smallest.false
persistenttrue keeps it on screen with the menu closed. Otherwise it is only shown while the menu is open.false
openWhether it is open from the start.false
local hud = ui.window{ title = "session", x = 24, y = 300, w = 240, h = 120, persistent = true, open = true }
local box = hud:group( "this round" )

#w:group( title ) → group

A group inside the window.

#w:open(), w:close(), w:toggle()

Open, close or flip the window. Opening a window that is already open is harmless.

#w:is_open() → boolean

#w:position( [x, y] ) → x, y

Moves the window when given coordinates, and returns where it is now.

#The controls

#g:toggle( label [, default] ) → control

A checkbox. get() is true or false. A toggle can also have a hotkey, set in the menu the way the cheat's own toggles get theirs or from code with c:set_key.

#g:slider( label, min, max [, default [, options]] ) → control

A slider between min and max; default is where it starts (the minimum if left out). options:

FieldMeaning
stepSnap to multiples of this, counted from min.
formatHow the value is shown, a printf-style field: "%d", "%.1f", "%d ms".
integertrue for a slider of whole numbers.

A slider is made of whole numbers by itself when step is a whole number of at least 1 and min is whole. For a whole-number slider get() returns an integer; otherwise a number. The format must contain exactly one field - %d for whole numbers, or %f, %g, %e (with optional width and precision, such as %.2f) for others; text around it is fine. Anything else is replaced by the default (%d, %.1f, or %.2f for steps below 0.1). If min is larger than max the two are swapped; if they are equal, max becomes min + 1.

#g:choice( label, items [, default_index] ) → control

One of several, from a drop-down. items is a non-empty list of strings. get() is the picked index, counting from 1; value() is the picked text.

local mode = group:choice( "mode", { "off", "assist", "full" }, 2 )

if mode:get( ) == 3 then end           -- "full"
if mode:value( ) == "assist" then end

#g:multi( label, items [, defaults] ) → control

Any number of several, from a drop-down of checkboxes. defaults is a list of booleans. get() is a list of booleans, one per item; get( 2 ) is the second one alone; value() is the list of the picked texts.

#g:color( label [, default [, alpha]] ) → control

A colour picker. default is a colour, white if left out; alpha (default true) says whether the picker has an alpha channel. get() is a colour.

#g:input( label [, default [, options]] ) → control

A line of text. options.max is the longest text it takes (1 to 1024, 128 by default); options.hint is the grey text shown while it is empty. get() is the text.

#g:key( label [, key [, mode]] ) → control

A key binding. key is a virtual key, 0 (none) by default; the user rebinds it in the menu. mode is "hold" (the default - active while the key is down), "toggle" (each press flips it) or "always". get() says whether it is active right now, and it follows the key whether or not the menu is open.

local boost = group:key( "boost key", input.vk.mouse4, "hold" )

on( "move", function( cmd )
	if boost:get( ) then
		cmd:press( btn.jump )
	end
end )

#g:button( label, handler ) → control

A button. handler is called, with no arguments, when it is clicked.

#g:label( text [, color] ) → control

A line of text in the group, in the menu's text colour unless you give one. c:rename( text ) changes it later.

#g:spacer( [height] ) → control

Empty space, 8 pixels high unless you say otherwise (0 to 400).

#g:separator() → control

A thin dividing line.

#g:list( label, items [, options] ) → control

A scrolling list to pick one row from. options.height is its height in pixels (40 to 600, 120 by default) and options.selected the row picked first. get() is the picked index counting from 1 (0 when the list is empty); value() the picked text. c:items() replaces the rows.

#g:image( texture [, width [, height]] ) → control

A picture in the group - a texture. Without sizes it is shown at the picture's own size; width and height override either one. The width is limited to what the group has room for. Nothing is shown until the texture is ready.

#g:custom{ options } → control

A rectangle of your own, filled by a function you write. See Custom controls.

#Working with controls

These methods exist on every control, with the exceptions noted. Calls that change the control return it, so they chain: group:slider( ... ):tooltip( "..." ).

#c:get( [item] ) → value

The control's value:

Kindget() returns
toggletrue / false
slidera number - an integer for a whole-number slider
choice, listthe picked index, from 1 (0 for an empty list)
multia list of booleans; get( i ) the single boolean for item i
colora colour
inputthe text
keywhether the key is active right now
the restnil

#c:set( value ) → control

Changes the value, with the same shapes as get() returns: a boolean, a number (clamped to the slider's range), an index, a colour, a string, a key code. For a multi, set( list ) takes a list of booleans, and set( index, true ) changes one item. Setting a value from code does not call on_change.

#c:value() → value

Like get(), but a choice or a list answers the picked text, and a multi answers the list of picked texts. Everything else is the same as get().

#c:on_change( handler ) → control

Calls handler( control ) after the user changes the control in the menu. c:on_change( nil ) takes the handler away.

local theme = group:choice( "theme", { "dark", "light" } )

theme:on_change( function( control )
	print( "theme is now " .. control:value( ) )
end )

#c:show( [visible] ) → control

Shows or hides the control. show() and show( true ) show it; show( false ) hides it. A hidden control keeps its value and is still saved.

#c:enable( [enabled] ) → control

Enables or disables it. A disabled control is shown as plain dim text - its label and current value - and cannot be changed.

#c:tooltip( text ) → control

The text shown when the pointer rests on the control.

#c:rename( text ) → control

Changes the label that is shown. The control keeps its saved value, because values are stored under the label it was created with.

#c:items( list ) → control

Replaces the entries of a choice, a multi or a list with a new list of strings. The picked index is kept as far as it still fits; a multi keeps its ticks when the number of items stays the same and starts clear when it changes. Any drop-down that is open is closed.

#c:set_range( min, max ) → control

Changes the range of a slider. The value is clamped into it.

#c:key() → integer

The virtual key bound to a toggle's hotkey, or the key of a key control; 0 when there is none.

#c:set_key( key [, mode] ) → control

Binds a key. On a toggle, mode is "toggle", "hold" or "hold_off"; on a key control it is "hold", "toggle" or "always". Leave it out to keep the current mode.

#c:click()

Presses a button from code, calling its handler. Only buttons have one.

#c:kind() → string

"toggle", "slider", "choice", "multi", "color", "text", "key", "button", "label", "spacer", "separator", "list", "image" or "custom".

#Custom controls

When none of the controls is the right one - a meter, a graph, a colour wheel, a little preview of what the script does - draw it yourself. g:custom reserves a rectangle in the group and calls your function every frame the control is visible:

local level = 0.5

group:custom{
	height = 28,

	draw = function( canvas, rect, io )
		if io.down then
			level = math.clamp( io.x / rect.w, 0, 1 )
		end

		canvas:fill( 0, 0, rect.w, rect.h, color( 0, 0, 0, 90 ), 6 )
		canvas:fill( 0, 0, rect.w * level, rect.h, ui.accent( ), 6 )
		canvas:text( 8, 6, string.format( "%d%%", math.floor( level * 100 + 0.5 ) ), color( 255, 255, 255 ), { shadow = true } )
	end,
}

The options table has two fields: height (8 to 800 pixels, 80 by default) and draw, which is required. The function receives three arguments.

canvas - a canvas that draws inside the control. Its origin 0, 0 is the control's top left corner and everything is cut off at its edges. It is the same canvas as in frame, except that c:glow does nothing and c:layer is refused.

rect - the control's rectangle as { x = 0, y = 0, w, h }: the width is whatever the group has room for, the height is the one you asked for.

io - what the user is doing, for this frame:

FieldMeaning
x, yThe mouse position relative to the control's top left corner. It keeps tracking while a drag that started on the control leaves it.
hoverThe pointer is over the control (and nothing is drawn on top of it).
downThe left button is held while the pointer is over the control, or while a drag that started on it carries on outside.
clickedThe left button went down this frame over the control.
releasedThe left button went up this frame over the control, or at the end of a drag that started on it.
right_clickedThe right button went down this frame over the control.
focusedThe last click landed on this control.
scrollWheel movement this frame while the pointer is over it.
keysA list of the virtual keys pressed this frame.
textThe text typed this frame, as one string.

keys and text are only filled while the control has the focus - its last click - or, when no control has it, while the pointer is over this one. So a custom text box works the way a real one does.

The function runs on the render thread under the usual time budget. A custom control is not saved - if its state should survive a restart, keep it in a store.

#Saving

The values of toggles, sliders, choices, multis, colours, text inputs and keys are saved, and come back the next time the script loads. A moment after you change one - not on every frame of a drag - the script's values are written to .data\<name>.values.json, and they are written again when the script unloads.

A value is stored under the tab, the group's title and the control's label it was created with. The consequences:

To reset a script, switch it off and delete .data\<name>.values.json.

#Notifications and the theme

#ui.notify( text [, options] )

Shows a toast in the corner of the screen, over everything including the menu. options:

FieldMeaningDefault
titleA bold line above the text.none
secondsHow long it stays, 0.5 to 30.4
colorThe colour of the stripe on its left.the accent colour

At most eight toasts are shown at once; a new one pushes the oldest out, so a script that notifies in a loop cannot bury the screen.

ui.notify( "saved", { title = "settings", seconds = 2, color = color( 120, 220, 140 ) } )

#ui.is_open() → boolean

Whether the menu is showing.

#ui.theme() → table

The menu's colours, so a script can match them: a table of colours with the keys accent, text, text_dim, window, child, button, checkbox, slider, card and elevated.

#ui.accent() → color

The accent colour - the same as ui.theme().accent.

Elysium scripting guide · built 2026-10-03