Controls overview

All fourteen controls, the options every one shares, and how handles work.

Every control is a method on a section. It takes one options table and returns a handle.

Luau
local handle = section:Toggle({ Text = "Enabled", Callback = print })

The fourteen#

Options every control shares#

Anything a control adds on top is documented on its own page.

These build the row itself:

OptionTypeDefaultWhat it does
TextstringThe label. Non-strings are coerced, so a number is safe.
DescriptionstringA second, quieter line beneath the label.
IconstringOne of the 1,573 built-in icon names.
IconColorColor3Overrides the icon colour for this control only.
IconSizenumber16Icon size in pixels.
LayoutOrdernumberExplicit ordering. Without it, controls appear in creation order.
FlatbooleanfalseDrops the card background and its border. Set for you on controls inside a Group.
HoverCardbooleantrueThe row lifts under the pointer. Never applies to a flat card.

These are read after the control is built, so they work the same on a control you registered yourself:

OptionTypeDefaultWhat it does
CallbackfunctionRuns when the value changes. On Button it runs on click, and on Keybind when the bound key is pressed.
Savestring | booleanPersists the value. true derives a key from the section and label; a string sets the key yourself.
FireOnStartbooleanfalseCalls Callback once at creation with whatever the control holds.
ReapplybooleanRe-applies the value when the character is replaced. Set false to opt out when the window sets ReapplyOnRespawn.
RevertOnClosebooleanApplies an off value when the window closes: false for a toggle, otherwise Default.
RevertToanyThe value RevertOnClose sends instead.
Applyfunction(value)What Reapply and RevertOnClose run instead of Callback. Needed on Button, Keybind and IconButton, whose Callback is an action rather than a value.
TooltipstringShown while the pointer is over the row.
DisabledbooleanfalseRefuses input and dims the control.
DisabledReasonstringShown as a tooltip while it is disabled.

Save, Reapply and RevertOnClose are covered in Saving settings and Lifecycle.

Title, Description and Separator return a Roblox instance instead of a handle, so nothing in the second table reaches them. Their options are on Title, Description & Separator.

Handles#

Stateful controls return a handle with Get and Set:

Luau
local speed = main:Slider({ Text = "Speed", Min = 0, Max = 100, Default = 50 })
 
speed:Get()      --> 50
speed:Set(80)    --> updates the UI and fires Callback

Set returns true, or false and a reason when the value is refused:

Luau
local ok, why = dropdown:Set("Nowhere")   --> false, "value is not present in Options"

Pass true as a second argument to set the value without firing anything. A panic button can put every control back that way without re-running the work.

Set usually fires the callback

On Toggle, Slider, Input, Dropdown, ColorPicker, Palette and Status, Set behaves as though the user moved the control, so calling it inside that control's own Callback loops forever. Keybind:Set is the exception: it fires Changed, not Callback.

Every handle carries these:

MethodWhat it does
Show() / Hide() / SetVisible(bool)Shows or hides the whole control
Destroy()Removes it from the page and disconnects its events
Name()Its label, the same string you passed as Text
Key()Its save key, or nil when the control was not given Save

handle.Instance is the card frame, for when you need the Roblox instance.

A handle also knows what it is, so you do not have to keep a parallel list of labels beside your config code:

Luau
local speed = main:Slider({ Text = "Walk speed", Save = true })
 
speed.Text      --> "Walk speed"
speed.Kind      --> "Slider"
speed.SaveKey   --> "My Script/Main/Slider/Walk speed"
speed.Section   --> the section it was built on

Name() and Key() are the same two values as methods. To walk every control in a window rather than one you already hold, see win:Controls().

Some controls add more. A dropdown, for example:

MethodWhat it does
Get()The current value: a string, or an array when Multi
Set(value)Selects a value or list of values
SetOptions(list)Replaces the option list in place
Open() / Close() / IsOpen()Controls the menu

Callbacks#

Callback fires whenever the value changes, with the new value as its first argument:

Luau
main:Toggle({
    Text = "Godmode",
    Callback = function(enabled)   -- boolean
        print("godmode:", enabled)
    end,
})
 
main:Slider({
    Text = "FOV", Min = 70, Max = 120,
    Callback = function(value)     -- number
        workspace.CurrentCamera.FieldOfView = value
    end,
})
 
main:Dropdown({
    Text = "Target", Options = { "Nearest", "Lowest HP" },
    Callback = function(choice)    -- string
        targetMode = choice
    end,
})

A callback does not fire when a control is created. One exception: a Saved control that restored a value calls it once with that value. See Saving settings.

Where the call came from#

Ember calls your callback for reasons other than the user touching the control: a restored value at startup, FireOnStart, a re-apply after a respawn, a revert on close. A third argument says which, so you do not have to guess from timing:

Luau
main:Toggle({
    Text = "Announce",
    Save = true,
    Callback = function(on, _, origin)
        if origin and origin.initial then return end   -- came off disk, stay quiet
        win:Notify({ Title = on and "On" or "Off" })
    end,
})
FieldSet when
initialThe call is the one at creation, from Save restoring or FireOnStart
restoredThat initial call had a value read back from disk
reappliedApply is running because the character was replaced
revertedThe window is closing and RevertOnClose is undoing it
keyThe control's save key, or nil

origin is nil for an ordinary user-driven change, so if origin and origin.initial is the whole test. The startup-suppression timer this replaces also swallowed real errors for as long as it was open.

FireOnStart covers the other case: fire once at creation even when nothing was saved, so a control that starts non-default applies itself.

A toggle can name the two halves separately with OnEnabled and OnDisabled. See Toggle.

Turning a control off#

Preview

My Script

v1.0

Search…
Ember v1

Developer mode

Unlocks the action below.

Start the farm

Runs until you stop it.

Debug logging
Verbose output

Every control takes Disabled, and every handle has the methods to change it later. A disabled control refuses input and looks like it is refusing.

Luau
local start = main:Button({
    Text = "Start the farm",
    ButtonText = "Go",
    Disabled = true,
    DisabledReason = "Pick a target first",
    Callback = beginFarm,
})
 
targetPicker:Set("Nearest")
start:Enable()
MethodWhat it does
SetDisabled(state, reason?)Turns it off or on. The reason is optional and sticks.
Disable(reason?) / Enable()The same thing, named
IsDisabled()Whether it is currently off

DisabledReason is shown as a tooltip while the control is disabled, so a dead button can say what would bring it back.

It refuses input rather than trusting the callback

There is no overlay. Every button inside the control stops accepting input and every coloured part is faded toward its background, so this works on a control you registered yourself. Guarding inside your callback still lets the control animate as though it did something.

Explaining a control#

Any control takes Tooltip, shown while the pointer is over it.

Luau
main:Slider({
    Text = "Tick rate",
    Min = 1, Max = 60, Default = 30,
    Tooltip = "How often the farm re-scans. Lower is cheaper.",
})

SetTooltip(text) changes it later, and SetTooltip(nil) removes it. DisabledReason takes priority while the control is disabled.

Organising a long section#

Title, Description and Separator break a section into runs. See Title, Description & Separator.

Group puts rows in a container that collapses. See Groups and search.

Adding your own#

RegisterControl installs a builder as a section method, so your control is called like any built-in one and gets the same card, search indexing, Reapply and handle methods:

Luau
Ember.RegisterControl("Badge", function(section, opts)
    local card, slot = Ember.Controls.mount(section, opts)
 
    local value = Ember.Util.label(opts.Value or "", 13, Ember.Theme.accent)
    value.Size = UDim2.new(0, 0, 0, 22)
    value.AutomaticSize = Enum.AutomaticSize.XY
    value.Parent = slot
 
    return {
        Instance = card,
        Set = function(_, text) value.Text = tostring(text) end,
    }
end)
 
main:Badge({ Text = "Build", Value = Ember.Version })

Return a table with an Instance and Ember adds Show, Hide, SetVisible, Destroy, SetTooltip, SetDisabled, Disable, Enable, IsDisabled, Name and Key to it, along with the Text, Kind, SaveKey and Section fields — so a control you wrote appears in win:Controls() beside the built-in ones.

The pieces that example is built from — Util.label, Controls.mount, the Metrics sizes and the rest — are in Utilities and internals.

CallWhat it does
Ember.RegisterControl(name, build)Installs it. Returns false, reason for a bad name, or one already taken.
Ember.UnregisterControl(name)Removes one of yours. Built-ins cannot be removed.
Ember.HasControl(name)Whether that name exists
Ember.ControlNames()Every control name, sorted

See them running#

Every control page carries a live preview of the real control, and the swatches above each one repaint it into any of the nine built-in themes. For all of them working together in one window, see the docs landing page or the full showcase.