SimplismUI is a client-side Roblox Luau UI library built around a straightforward hierarchy:
Library → Window → Tab → Column → Section → Elements
Tabs default to one column; tab:NewSection(...) creates a section in column 1.
It provides animated windows, scrollable tabs and sections, common settings controls, searchable and multi-select dropdowns, keybinds, a full color picker, notifications, theming, preset colors, and an optional key-authentication interface.
- Features
- Requirements
- Installation
- Quick Start
- Windows
- Tabs
- Sections
- UI Elements
- Theming
- Preset Colors
- Notifications
- Authentication UI
- Cleanup
- Complete Example
- Edge Cases & Notes
- Draggable, animated, responsive windows (or fixed-size)
- Show, hide, toggle, and minimize controls, with an optional rebindable toggle key
- Top, left, bottom, or right tab bars with upright, text-sized buttons and automatic sidebar width
- External visibility-button integration
- Scrolling tab bar and responsive multi-column tabs with indexed section creation
- Collapsible sections and ordered elements via
ListOrder - Option grids with responsive square cells or custom
UDimdimensions - Buttons, sliders, toggles, labels, single/multi-select dropdowns (with search), text boxes, keybinds
- HSV/RGB/HEX color picker with configurable presets
- Window-local toast notifications and global notifications
- Optional authentication/key-system UI
- Configurable theme palette
- Mouse and touch support throughout
- Cleanup and destruction methods
SimplismUI is intended for client-side Roblox code and should be required from a LocalScript or equivalent client context.
The repository's source.luau returns the Library table directly. Place it inside a ModuleScript and require it from client-side code:
local Library = require(script.Parent.SimplismUI)There is no package manager or HTTP-loading API — just require the module.
local Library = require(script.Parent.SimplismUI)
local window = Library:Window({
Title = "SimplismUI Example",
ToggleKeybind = Enum.KeyCode.RightShift,
})
local mainTab = window:AddTab({
Title = "Main",
})
local generalSection = mainTab:NewSection({
Title = "General",
})
generalSection:AddLabel({
Text = "Welcome to SimplismUI",
})
generalSection:AddToggle({
Text = "Enabled",
Default = true,
Callback = function(enabled)
print("Enabled:", enabled)
end,
})
generalSection:AddSlider({
Text = "Volume",
Min = 0,
Max = 100,
Default = 50,
Increment = 1,
Callback = function(value)
print("Volume:", value)
end,
})
generalSection:AddButton({
Text = "Notify",
Callback = function()
window:Toast({
Text = "Button pressed",
Type = "Success",
})
end,
})The first tab added to a window becomes active automatically.
local window = Library:Window({
Title = "Settings",
})Library:Window() also accepts no configuration table.
| Field | Type | Default | Description |
|---|---|---|---|
Title |
string? |
"Settings" |
Window title |
TabBarLocation |
"Top" | "Left" | "Bottom" | "Right"? |
"Top" |
Positions the tab bar; other values raise an assertion |
Size |
UDim2? |
None | Custom size, used when OverrideSize is enabled |
OverrideSize |
boolean? |
false |
Uses Size as the base size; sidebar width is still added |
Parent |
ScreenGui? |
New UUID |
Existing ScreenGui to contain the window |
StartVisible |
boolean? |
true |
Initial open state |
ToggleKeybind |
Enum.KeyCode? |
None | Adds a rebindable top-bar key that toggles the window |
If no custom Parent is supplied, SimplismUI creates its own ScreenGui with a UUID as its name.
The parent container is chosen in this order:
gethui()— if availableCoreGuiPlayers.LocalPlayer.PlayerGui
A custom Parent overrides this behavior.
Without OverrideSize, the window targets UDim2.fromOffset(460, 520) and automatically shrinks on narrower viewports, updating live as the viewport changes. Set OverrideSize = true and provide Size to use a fixed base size instead. Left and right tab bars add their measured width to that base size:
local window = Library:Window({
Title = "Custom Window",
OverrideSize = true,
Size = UDim2.fromOffset(600, 450),
})local window = Library:Window({
Title = "Settings",
TabBarLocation = "Left",
ToggleKeybind = Enum.KeyCode.RightShift,
})
window:AddTab({ Title = "Main" })
window:AddTab({ Title = "Appearance" })| Location | Tab layout | Space used |
|---|---|---|
Top |
Horizontal, below the title bar | 40-pixel tab bar; default placement |
Bottom |
Horizontal, along the bottom | 40-pixel tab bar |
Left |
Vertical, below the title bar | Width of the widest visible tab button plus 16 pixels of horizontal padding |
Right |
Vertical, below the title bar | Width of the widest visible tab button plus 16 pixels of horizontal padding |
Buttons stay upright in every position and are 32 pixels high. Their widths use the title's measured text width plus 38 pixels, clamped to 38–100 pixels. Long titles truncate with an ellipsis. Buttons are separated by 4 pixels; overflowing tab lists scroll horizontally for top/bottom bars and vertically for side bars.
Side bars expand the window by their actual required width, preserving the base content width. This is not a fixed 116-pixel increase: 116 pixels is only the maximum, reached with a 100-pixel button. Adding, hiding, or showing tabs recalculates the sidebar and window width from the visible buttons. An empty sidebar adds no width. This adjustment also applies when OverrideSize = true; viewport-driven responsive sizing continues to apply when it is false.
TabBarLocation is a construction option; there is no public setter to relocate the bar after creation.
| Method | Parameters | Returns | Description |
|---|---|---|---|
AddTab(config) |
TabConfig |
TabObject |
Creates a tab |
SwitchTab(tab) |
TabObject |
nil |
Activates a tab and deactivates the previous one, keeping window.ActiveTab in sync |
SetVisible(visible?) |
boolean? |
nil |
Shows, hides, or toggles when omitted |
GetVisible() |
None | boolean |
Current open state |
Toggle() |
None | nil |
Toggles visibility |
Minimize() |
None | nil |
Toggles minimized state |
AttachVisibilityButton(config) |
VisibilityButtonConfig |
nil |
Connects an external button to toggle the window |
Toast(config?) |
ToastConfig? |
ToastObject |
Creates a window-local toast (see Notifications) |
Destroy() |
None | nil |
Destroys the window and its managed content |
SetVisible(false) hides the window and preserves the object for later use; the close button in the top bar calls this rather than destroying the window. Minimize() shrinks the window to just its top bar; calling it again restores the full window.
local window = Library:Window({
Title = "Settings",
ToggleKeybind = Enum.KeyCode.RightShift,
})The current key is shown in the top bar; clicking it enters rebinding mode (keyboard only, Escape cancels). There is no public getter/setter for this key after construction — it can only be rebound through its top-bar UI.
local button = Instance.new("TextButton")
button.Size = UDim2.fromOffset(120, 36)
button.Parent = window.ScreenGui
window:AttachVisibilityButton({
Button = button,
ShowText = "Open UI",
HideText = "Close UI",
})| Field | Type | Default | Description |
|---|---|---|---|
Button |
GuiButton |
Required | External button to wire up |
ShowText |
string? |
"Show" |
Text while the window is hidden |
HideText |
string? |
"Hide" |
Text while the window is visible |
For a TextButton, SimplismUI updates its Text. For an ImageButton, it dims the image while the window is hidden and restores it when visible. Other GuiButton subclasses still toggle the window but get no built-in visual feedback.
ShowText/HideText are stored per-window, not per-button — attaching a second button with different text replaces the pair used for all attached TextButtons. There's no detach method, and the connection isn't cleaned up by the window's own Destroy(); stop using or destroy an external button yourself if it can outlive the window.
local generalTab = window:AddTab({
Title = "General",
Columns = 3,
})| Field | Type | Default | Description |
|---|---|---|---|
Title |
string? |
"Tab" |
Tab title |
Columns |
number? |
1 |
Positive finite integer; creates this many columns |
MaxColumnWidth |
UDim? |
None | Caps each column's width, subject to the minimum width; scale is relative to window.ContentArea.AbsoluteSize.X |
Use one-based indexing to choose a column. tab:NewSection(config) is equivalent to tab[1]:NewSection(config):
local controls = generalTab:NewSection({ Title = "Controls" })
local inputs = generalTab[2]:NewSection({ Title = "Inputs" })
local selection = generalTab[3]:NewSection({ Title = "Selection" })
controls:AddToggle({ Text = "Enabled", Default = true })
inputs:AddTextBox({ Text = "Name" })
selection:AddOptionGrid({
Text = "Mode",
CellsInFillDirection = 3,
Options = {
{ Name = "Normal" },
{ Name = "Fast" },
{ Name = "Precise" },
},
})Columns share the tab's scroll frame and stack their own sections vertically. Their widths update when the content area or scroll viewport resizes. Available width is divided equally after subtracting 8-pixel outer padding on each side and 8-pixel gaps between columns. Each column has a minimum width of math.max(200, window.ContentArea.AbsoluteSize.X / 2.5).
MaxColumnWidth resolves to contentWidth * Scale + Offset and caps the calculated width. The minimum takes precedence if that cap is smaller. For example, MaxColumnWidth = UDim.new(0, 400) limits columns to 400 pixels only when the minimum permits it.
The tab scroll frame uses AutomaticCanvasSize = Enum.AutomaticSize.XY and ScrollingDirection = Enum.ScrollingDirection.XY when there are more than two columns or the combined widths, gaps, and padding exceed the viewport. Otherwise both use Y. The viewport itself stays fixed; its AutomaticSize is not changed.
Valid indices run from 1 through Columns; indexing does not create additional columns. Each column exposes Index, Frame, Tab, and NewSection(config). All sections remain tracked in tab.Sections, and section.Tab still refers to the owning tab. There is no public column-count setter.
| Method | Parameters | Returns | Description |
|---|---|---|---|
NewSection(config) |
SectionConfig |
SectionObject |
Creates a section in column 1 |
SetActive(active) |
boolean |
nil |
Changes this tab's own active/visual state only — does not update window.ActiveTab; prefer window:SwitchTab(tab) for normal navigation |
SetVisible(visible) |
boolean |
nil |
Shows or hides the tab |
Destroy() |
None | nil |
Cleans up sections and managed tab connections |
The first tab added to a window becomes active automatically. Hiding the currently active tab deactivates its content but does not select another tab or clear window.ActiveTab — switch away first if that matters:
window:SwitchTab(generalTab)
playerTab:SetVisible(false)local section = generalTab:NewSection({
Title = "General Settings",
StartCollapsed = false,
ListConfig = {
Padding = UDim.new(0, 8),
},
})| Field | Type | Default | Description |
|---|---|---|---|
Title |
string? |
"Section" |
Section title |
StartCollapsed |
boolean? |
false |
Starts the section collapsed |
ListConfig |
{ [string]: any }? |
None | Raw properties applied directly to the section's UIListLayout (e.g. to change SortOrder, which changes how ListOrder behaves) |
| Method | Returns |
|---|---|
SetVisible(visible) |
nil |
AddButton(config) |
ButtonElement |
AddSlider(config) |
SliderElement |
AddToggle(config) |
ToggleElement |
AddLabel(config) |
LabelElement |
AddDropdown(config) |
DropdownElement |
AddMultiSelectDropdown(config) |
MultiSelectDropdownElement |
AddOptionGrid(config) |
OptionGridElement |
AddTextBox(config) |
TextBoxElement |
AddKeybind(config) |
KeyInputElement |
AddColorPicker(config) |
ColorPickerElement |
Destroy() |
nil |
Sections auto-resize to their content, animate when collapsed/expanded, and clip their content while collapsed. There is no public SetCollapsed method — the header is clicked by the user to toggle it.
Every element accepts ListOrder (default 0), which controls layout order within the section:
section:AddLabel({ Text = "First", ListOrder = 1 })
section:AddButton({ Text = "Second", ListOrder = 2, Callback = function() end })All element constructors take a configuration table. Most elements expose a root Frame, type-specific Roblox instances, SetVisible(boolean) (hides/shows without invoking any callback), and Destroy().
| Element | User interaction | Programmatic setter | Initial default |
|---|---|---|---|
| Button | Yes | N/A | No |
| Slider | Yes | SetValue fires |
No, including NaN defaults |
| Toggle | Yes | SetValue fires |
No |
| Label | None | N/A | N/A |
| Dropdown | Yes | SetSelectedOption fires |
No |
| Multi-select | Yes | SetSelected / ClearSelected fire |
No |
| Option Grid | Yes | SetSelectedOption fires |
No |
| TextBox | On focus loss | SetText fires |
No |
| Keybind | Binding changes | SetInput / ClearInput fire |
No |
| Color picker | Color changes | SetColor fires |
No |
Callbacks run synchronously, directly from the triggering event or setter — a long-running callback occupies that thread.
local button = section:AddButton({
Text = "Run",
Callback = function()
print("Run")
end,
})
button:SetText("Run Again")| Field | Type | Default | Description |
|---|---|---|---|
Text |
string? |
"Button" |
Button text |
ListOrder |
number? |
0 |
Layout order |
Callback |
(() -> ())? |
None | Runs when the button is pressed |
SetVisible(boolean), SetText(string), Destroy()
Frame (Frame), Button (TextButton)
local slider = section:AddSlider({
Text = "Speed",
Min = 0,
Max = 20,
Default = 5,
Increment = 0.5,
Decimals = 1,
Callback = function(value)
print(value)
end,
})
slider:SetValue(7.5)
local value = slider:GetValue()| Field | Type | Default | Description |
|---|---|---|---|
Text |
string? |
"Slider" |
Slider title |
Min |
number? |
0 |
Minimum |
Max |
number? |
100 |
Maximum (must be > Min) |
Default |
number? |
Min |
Initial value |
Increment |
number? |
1 |
Rounding increment; 0 disables rounding |
Decimals |
number? |
2 when Increment < 1, otherwise 0 |
Display precision only |
ListOrder |
number? |
0 |
Layout order |
Callback |
((number) -> ())? |
None | Receives the current value |
Interactive changes and SetValue round to the nearest multiple of Increment (anchored to zero, not Min) and clamp to [Min, Max]. Default is used as-is at construction and is not passed through this rounding/clamping — keep it within range and aligned to your increment.
Construction never invokes the slider callback, including when Default = 0/0.
The slider also has a numeric text entry (invalid text on focus loss reverts to the current value) and supports mouse/touch dragging, firing the callback continuously while dragging.
SetVisible(boolean), SetValue(number) (fires Callback), GetValue() → number, Destroy()
local toggle = section:AddToggle({
Text = "Auto Save",
Default = false,
Callback = function(enabled)
print(enabled)
end,
})
toggle:SetValue(true)
local enabled = toggle:GetValue()| Field | Type | Default |
|---|---|---|
Text |
string? |
"Toggle" |
Default |
boolean? |
false |
ListOrder |
number? |
0 |
Callback |
((boolean) -> ())? |
None |
The initial state does not invoke the callback.
SetVisible(boolean), SetValue(boolean) (fires Callback), GetValue() → boolean, Destroy()
Frame (Frame), Label (TextLabel)
local label = section:AddLabel({
Text = "Status: Ready",
})
label:SetText("Status: Running")
local text = label:GetText()| Field | Type | Default |
|---|---|---|
Text |
string? |
"Label" |
ListOrder |
number? |
0 |
SetVisible(boolean), SetText(string), GetText() → string, Destroy()
Frame (Frame), Label (TextLabel)
local dropdown = section:AddDropdown({
Text = "Mode",
Options = { "Normal", "Fast", "Safe" },
Default = "Normal",
Searchable = true,
Callback = function(option)
print(option)
end,
})
dropdown:AddOption("Custom")
dropdown:SetSelectedOption("Fast")
local selected = dropdown:GetSelected()| Field | Type | Default | Description |
|---|---|---|---|
Text |
string? |
"Select..." |
Placeholder when nothing is selected |
Options |
{string} |
{} if omitted |
Available options |
Default |
string? |
None | Initial selection |
Searchable |
boolean? |
false |
Adds a search field when open |
ListOrder |
number? |
0 |
Layout order |
Callback |
((string) -> ())? |
None | Receives the selected option |
A valid Default initializes the selection without invoking Callback; an invalid one is silently ignored.
| Method | Parameters | Returns | Fires callback? |
|---|---|---|---|
SetVisible |
boolean |
nil |
No |
AddOption |
string |
nil |
No |
RemoveOption |
string |
nil |
No — if the removed option was selected, selection is cleared without firing the callback |
SetOptions |
{string} |
nil |
No — replaces options and clears selection |
SetSelectedOption |
string |
nil |
Yes, if the option exists (silently ignored otherwise) |
GetSelected |
None | string? |
No |
Destroy |
None | nil |
— |
When Searchable = true, matching is case-insensitive substring search; the query clears each time the dropdown opens or closes. The option list becomes scrollable once it's long.
local dropdown = section:AddMultiSelectDropdown({
Text = "Targets",
Options = { "Players", "NPCs", "Objects" },
Default = { "Players" },
MaxSelected = 2,
Searchable = true,
Callback = function(selected)
print(table.concat(selected, ", "))
end,
})
dropdown:SetSelected({ "Players", "Objects" })
local selected = table.clone(dropdown:GetSelected())| Field | Type | Default | Description |
|---|---|---|---|
Text |
string? |
"Select..." |
Placeholder with no selections |
Options |
{string} |
{} if omitted |
Available options |
Default |
{string} | string? |
None | Initial selection(s); invalid entries are dropped |
MaxSelected |
number? |
math.huge |
Maximum selected entries |
Searchable |
boolean? |
false |
Adds search |
ListOrder |
number? |
0 |
Layout order |
Callback |
(({string}) -> ())? |
None | Receives the full selection table |
Selected values display joined with ", ". The dropdown stays open when toggling a selection. A valid Default initializes the selections without invoking Callback.
Once MaxSelected is reached, clicking an unselected option doesn't add it — but the callback still fires with the unchanged selection.
| Method | Parameters | Returns | Fires callback? |
|---|---|---|---|
SetVisible |
boolean |
nil |
No |
AddOption |
string |
nil |
No |
RemoveOption |
string |
nil |
No |
SetOptions |
{string} |
nil |
No |
SetSelected |
{string} |
nil |
Yes |
GetSelected |
None | {string} |
No |
ClearSelected |
None | nil |
Yes |
Destroy |
None | nil |
— |
SetSelected replaces the whole selection: unknown values are ignored and it stops adding once MaxSelected is hit. It does not deduplicate its input, so pass a list of unique values.
GetSelected() returns the library's actual internal table, and the callback receives that same table — clone it before mutating (table.clone(dropdown:GetSelected())). Mutating it directly bypasses MaxSelected, option validation, and UI updates.
local optionGrid = section:AddOptionGrid({
Text = "Sword Selector",
CellsInFillDirection = 3,
Options = {
{
Name = "Base Sword",
Icon = "rbxassetid://1234567890",
Color = Color3.fromRGB(60, 60, 80),
},
{
Name = "Fire Sword",
Icon = "rbxassetid://1234567891",
},
},
Default = "Base Sword",
Searchable = true,
Callback = function(name)
print(name)
end,
})
optionGrid:AddOption({
Name = "Ice Sword",
Icon = "rbxassetid://1234567892",
})
optionGrid:AddOptions({
{ Name = "Storm Sword", Icon = "rbxassetid://1234567893" },
{ Name = "Shadow Sword", Icon = "rbxassetid://1234567894" },
})
optionGrid:RemoveOption("Ice Sword")
optionGrid:SetSelectedOption("Fire Sword")
local selected = optionGrid:GetSelected()| Field | Type | Default | Description |
|---|---|---|---|
Text |
string? |
"Option Grid" |
Title shown above the grid |
Options |
{OptionGridItem} |
{} if omitted |
Available options, each with Name, optional Icon, and optional Color |
Default |
string? |
None | Initial selection; must match a Name in Options |
Searchable |
boolean? |
false |
Adds a search field above the grid, same matching as Dropdown/Multi-Select |
CellSizeX |
UDim? |
UDim.new(0, 64) |
Cell width; overridden by CellsInFillDirection |
CellSizeY |
UDim? |
UDim.new(0, 64) without automatic sizing; otherwise matches computed width |
Cell height; an explicit value overrides automatic square sizing |
CellsInFillDirection |
number? |
None | Positive finite integer; automatically fits this many cells across a row and overrides MaxCellsX |
HorizontalAlignment |
"Left" | "Center" | "Right"? |
"Left" |
Grid's horizontal alignment within the section |
VerticalAlignment |
"Top" | "Center" | "Bottom"? |
"Top" |
Grid's vertical alignment within the section |
FillDirectionX |
"Left" | "Right"? |
"Right" |
Direction cells fill across a row |
FillDirectionY |
"Up" | "Down"? |
"Down" |
Direction rows stack |
MaxCellsX |
number? |
None | Maximum columns per row (see Layout below) |
ListOrder |
number? |
0 |
Layout order |
Callback |
((string) -> ())? |
None | Receives the selected option's Name |
Each Options entry:
| Field | Type | Default | Description |
|---|---|---|---|
Name |
string |
Required | Unique identifier and display text; used as the lookup key for AddOption/RemoveOption/SetSelectedOption, so keep values unique |
Icon |
string? |
None | Asset ID for the cell's image. If present, Name renders as a label docked to the bottom-center of the cell. If absent, Name renders centered in the cell instead |
Color |
Color3? |
Theme Element color |
Cell background color |
A valid Default initializes the selection without invoking Callback. An unmatched Default logs a warning and leaves the grid with nothing selected rather than erroring.
The grid fills horizontally. FillDirectionX and FillDirectionY select the starting corner and direction; they do not switch the layout to vertical filling.
With CellsInFillDirection = n, cell width is calculated as (gridWidth - 8 - 6 * (n - 1)) / n, with a minimum of 1 pixel. The grid has 4-pixel outer padding on each side and 6-pixel gaps. Width calculations use the scroll frame's full width, independently of scrollbar visibility, so a scrollbar appearing cannot repeatedly shrink and enlarge square cells. Extremely narrow containers cannot fit arbitrary counts once cells reach the 1-pixel minimum.
When CellSizeY is omitted, automatic sizing makes cells square and updates both dimensions on resize. Provide CellSizeY to keep automatic widths with a custom height:
local flatGrid = section:AddOptionGrid({
Text = "Mode",
CellsInFillDirection = 3,
CellSizeY = UDim.new(0, 28),
Options = {
{ Name = "Normal" },
{ Name = "Fast" },
{ Name = "Precise" },
},
})Without CellsInFillDirection, CellSizeX and CellSizeY supply the UIGridLayout.CellSize dimensions directly, including their scale and offset components. MaxCellsX only limits the number of cells per row; it does not stretch cells. For manually sized rectangular cells:
local customGrid = section:AddOptionGrid({
Text = "Custom Cells",
CellSizeX = UDim.new(0, 90),
CellSizeY = UDim.new(0, 28),
MaxCellsX = 3,
Options = {
{ Name = "One" },
{ Name = "Two" },
{ Name = "Three" },
},
})The expanded cell viewport is capped at 200 pixels high, with an additional 38 pixels for search when enabled. Offset-only heights use the calculated row count, gaps, and outer padding; a nonzero CellSizeY.Scale uses the full 200-pixel viewport to give relative heights a stable reference. Content exceeding the viewport scrolls vertically. Resizing recalculates the open dropdown's height.
Automatic sizing applies to the dropdown cells, not the selected-option preview in the header. The preview continues to use the configured dimension offsets, defaulting to 64 pixels per axis.
Migration: CellSize is no longer a supported configuration field. Replace CellSize = UDim2.new(xScale, xOffset, yScale, yOffset) with CellSizeX = UDim.new(xScale, xOffset) and CellSizeY = UDim.new(yScale, yOffset). Omit CellSizeY when using CellsInFillDirection if you want automatic squares.
| Method | Parameters | Returns | Fires callback? |
|---|---|---|---|
SetVisible |
boolean |
nil |
No |
AddOption |
OptionGridItem |
nil |
No |
AddOptions |
{OptionGridItem} |
nil |
No — adds many options and rebuilds the grid once; use this instead of calling AddOption in a loop for large batches |
RemoveOption |
string |
nil |
No — if the removed option was selected, selection clears silently |
SetSelectedOption |
string |
nil |
Yes, if the option exists (silently ignored otherwise) |
GetSelected |
None | string? |
No |
Destroy |
None | nil |
— |
When Searchable = true, matching is case-insensitive substring search, same as Dropdown.
local input = section:AddTextBox({
Text = "Username",
PlaceHolder = "Type here...",
Callback = function(text)
print(text)
end,
})
input:SetText("Simplism")
local text = input:GetText()| Field | Type | Default |
|---|---|---|
Text |
string? |
"Input" |
PlaceHolder |
string? |
"Type here..." |
ListOrder |
number? |
0 |
Callback |
((string) -> ())? |
None |
There's no field for initial input text — the input starts empty. The callback fires on focus loss (Enter is not required) and on SetText, but not during construction. The control widens into a two-line layout automatically if the title no longer fits alongside the input.
SetVisible(boolean), SetText(string), GetText() → string, Destroy()
Frame (Frame), TextBox (TextBox)
local keybind = section:AddKeybind({
Text = "Open Menu",
Default = Enum.KeyCode.F,
Clearable = true,
ListenForInputEnded = false,
Callback = function(key)
print("Binding:", key)
end,
InputPressedCallback = function(key)
print("Pressed:", key)
end,
})
keybind:SetInput(Enum.KeyCode.G)
local key = keybind:GetInput()| Field | Type | Default | Description |
|---|---|---|---|
Text |
string? |
"Keybind" |
Label |
Default |
Enum.KeyCode? |
nil |
Initial key |
ListOrder |
number? |
0 |
Layout order |
Clearable |
boolean? |
false |
Shows a clear (×) button |
ListenForInputEnded |
boolean? |
false |
Capture the new key on release rather than press |
Callback |
((Enum.KeyCode?) -> ())? |
None | Runs when the binding changes |
InputPressedCallback |
((Enum.KeyCode) -> ())? |
None | Runs when the bound key is pressed |
The initial Default does not invoke Callback. Rebinding is keyboard-only; Escape cancels. InputPressedCallback never fires while rebinding and ignores gameProcessed input.
keybind:ClearInput() works regardless of Clearable — that option only controls whether the visible clear button is shown, and clearing invokes Callback(nil).
| Method | Parameters | Returns |
|---|---|---|
SetVisible |
boolean |
nil |
SetInput |
Enum.KeyCode? |
nil |
GetInput |
None | Enum.KeyCode? |
ClearInput |
None | nil |
SetInputPressedCallback |
((Enum.KeyCode) -> ())? |
nil |
Destroy |
None | nil |
SetInput and ClearInput fire Callback. SetInputPressedCallback replaces the pressed-key callback without invoking it.
Frame (Frame), Button (TextButton), Label (TextLabel)
local picker = section:AddColorPicker({
Text = "Accent Color",
Default = Color3.fromRGB(88, 101, 242),
Callback = function(color)
print(color)
end,
})
picker:SetColor(Color3.fromRGB(255, 100, 100))
local color = picker:GetColor()| Field | Type | Default |
|---|---|---|
Text |
string? |
"Color" |
Default |
Color3? |
Color3.fromRGB(255, 0, 0) |
ListOrder |
number? |
0 |
Callback |
((Color3) -> ())? |
None |
The initial default does not invoke the callback.
Opening the picker shows a saturation/value field, a hue strip, HSV/RGB/HEX text inputs, and preset swatches (see Preset Colors below). Hue accepts 0–360; saturation, value, and RGB channels accept 0–100/0–255 respectively and are clamped. HEX accepts RGB, #RGB, RRGGBB, or #RRGGBB (case-insensitive); invalid text reverts to the current color. The callback fires on any change — dragging, valid text input, preset selection, or SetColor.
Only one picker panel can be open per window; opening another, or clicking outside, closes the current one. The panel positions itself near the picker element and adapts if there isn't room.
SetVisible(boolean), SetColor(Color3), GetColor() → Color3, Destroy()
Frame (Frame), Preview (Frame), Title (TextLabel), Window (the owning window object)
Library:SetTheme({
Accent = Color3.fromRGB(120, 90, 255),
AccentHover = Color3.fromRGB(145, 120, 255),
Background = Color3.fromRGB(20, 20, 28),
})SetTheme requires a table, warns on unknown keys, and does not validate that supplied values are actually Color3s (bad values can cause later property/tween errors). It does not repaint already-created UI — set the theme before constructing windows and elements for consistent styling; some already-created elements may still pick up new values later since some tween paths read the palette at interaction time.
| Property | Default | Primary usage |
|---|---|---|
Background |
Color3.fromRGB(25, 25, 35) |
Window/auth backgrounds |
TopBar |
Color3.fromRGB(30, 30, 45) |
Window/auth title bars |
TabBar |
Color3.fromRGB(22, 22, 32) |
Tab-bar background |
TabActive |
Color3.fromRGB(88, 101, 242) |
Active tabs |
TabInactive |
Color3.fromRGB(40, 40, 55) |
Inactive tab controls |
TabHover |
Color3.fromRGB(50, 50, 70) |
Tab/control hover states |
Section |
Color3.fromRGB(32, 32, 48) |
Section body |
SectionHeader |
Color3.fromRGB(38, 38, 56) |
Section headers |
Element |
Color3.fromRGB(40, 40, 58) |
Element surfaces |
ElementHover |
Color3.fromRGB(48, 48, 68) |
Element hover/listening state |
Accent |
Color3.fromRGB(88, 101, 242) |
Main accent color |
AccentHover |
Color3.fromRGB(108, 121, 255) |
Accent hover state |
Text |
Color3.fromRGB(220, 220, 235) |
Primary text |
TextDim |
Color3.fromRGB(140, 140, 165) |
Secondary text |
TextMuted |
Color3.fromRGB(90, 90, 115) |
Placeholder/muted text |
Border |
Color3.fromRGB(50, 50, 70) |
General strokes/borders |
SliderTrack |
Color3.fromRGB(50, 50, 70) |
Slider track |
SliderFill |
Color3.fromRGB(88, 101, 242) |
Slider fill |
ToggleOff |
Color3.fromRGB(60, 60, 80) |
Disabled toggles/checks |
ToggleOn |
Color3.fromRGB(88, 101, 242) |
Enabled toggles/checks |
InputBg |
Color3.fromRGB(28, 28, 42) |
Input backgrounds |
DropdownBg |
Color3.fromRGB(28, 28, 42) |
Dropdown/picker panels |
DropdownHover |
Color3.fromRGB(45, 45, 65) |
Dropdown option hover |
Shadow |
Color3.fromRGB(0, 0, 0) |
Window shadow |
CloseBtn |
Color3.fromRGB(255, 75, 75) |
Close controls |
CloseBtnHover |
Color3.fromRGB(255, 100, 100) |
Close-control hover |
NotificationBg |
Color3.fromRGB(32, 32, 48) |
Toast/notification background default |
NotificationBorder |
Color3.fromRGB(88, 101, 242) |
Global notification border default |
Library.Theme ({ TextSize = 14, TextColor = Color3.fromRGB(220, 220, 235) }) supplies default text size/color for Window:Toast(). It's independent from the palette above — changing Library:SetTheme({ Text = ... }) does not update Library.Theme.TextColor; set both if you want them to match. Library:Notify() uses the Text/TextDim palette directly instead.
Library:SetPresetColors({
Color3.fromRGB(255, 0, 0),
Color3.fromRGB(0, 255, 0),
Color3.fromRGB(0, 0, 255),
Color3.fromRGB(255, 255, 255),
})SimplismUI includes a default rainbow-style preset palette shown in every color picker. SetPresetColors replaces it, stores the table directly (see Notes on ownership), and does not rebuild color pickers that already exist — set presets before constructing pickers that should use them.
SimplismUI has two independent notification systems:
Window:Toast()— configurable, window-local, stacked toasts.Library:Notify()— simpler global bottom-right notifications.
local toast = window:Toast({
Text = "Settings saved",
Type = "Success",
Position = "BottomRight",
Duration = 4,
})Window:Toast() may be called with no configuration table.
| Field | Type | Default | Description |
|---|---|---|---|
Text |
string? |
"" |
Toast text (supports rich text) |
Duration |
number? |
5 |
Auto-dismiss duration; 0 or negative disables auto-dismiss and the duration bar |
Position |
ToastPosition? |
"Bottom" |
Top/TopLeft/TopRight/Bottom/BottomLeft/BottomRight, or a Vector2 for a custom centered scale position |
Type |
ToastType? |
"Library" |
Library/Info/Success/Warning/Error — sets the default border color |
TextSize |
number? |
Library.Theme.TextSize |
Text size |
TextColor |
Color3? |
Library.Theme.TextColor |
Text color |
BackgroundColor |
Color3? |
NotificationBg |
Background |
BorderColor |
Color3? |
Type's default color | Border/accent |
ShowDurationBar |
boolean? |
true |
Enables the duration bar |
DurationBarColor |
Color3? |
BorderColor |
Duration-bar color |
EntranceDuration |
number? |
0.35 |
Duration, in seconds, of the toast's entrance animation |
ExitDuration |
number? |
0.25 |
Duration, in seconds, of the toast's exit animation |
Dismissible |
boolean? |
true |
Allow mouse/touch dismissal |
MaxWidth |
number? |
360 |
Maximum width |
Spacing |
number? |
8 |
Stack spacing |
SafeAreaPadding |
number? |
16 |
Edge padding for named positions |
MaxStackSize |
number? |
8 |
Maximum toasts stacked at once — oldest is dropped once exceeded |
PauseOnHover |
boolean? |
true |
Hovering pauses the countdown and duration bar |
Toasts sharing a position share one stack container; the first active toast at that position effectively sets Spacing/SafeAreaPadding for the whole stack until it empties — configure those consistently for toasts at the same position.
| Method | Parameters | Returns | Description |
|---|---|---|---|
Dismiss |
None | nil |
Plays the exit animation, then removes the toast |
Destroy |
None | nil |
Removes it immediately |
ResetDuration |
None | nil |
Restarts its configured duration |
SetText |
string |
nil |
Updates text and recalculates width |
A persistent toast: Duration = 0, Dismissible = false, removed later with toast:Dismiss().
Library:Notify({
Title = "Success",
Content = "Configuration saved.",
Duration = 3,
})Unlike Window:Toast, Library:Notify requires a configuration table. It uses a separate shared NotificationUI bottom-right container, animates in, can be dismissed by mouse/touch, auto-closes after Duration, and does not return a controller object. Its auto-close timer starts immediately at creation (Window:Toast's starts after the entrance animation finishes), so very short durations can dismiss before the entrance/duration-bar animation has fully played.
| Field | Type | Default |
|---|---|---|
Title |
string? |
"Notification" |
TitleTextSize |
number? |
14 |
Content |
string? |
"" |
ContentTextSize |
number? |
12 |
Duration |
number? |
3 |
NotificationColor |
Color3? |
Theme NotificationBg |
NotificationBorderColor |
Color3? |
Theme NotificationBorder |
TitleTextColor |
Color3? |
Theme Text |
ContentTextColor |
Color3? |
Theme TextDim |
DurationBarColor |
Color3? |
NotificationBorderColor |
local auth = Library:NewAuth({
Title = "Access",
GetKey = function()
print("Provide the key acquisition flow")
end,
Auth = function(key)
return key == "example-key"
end,
OnSuccess = function()
print("Authenticated")
end,
})The configuration table is optional.
| Field | Type | Default | Description |
|---|---|---|---|
Title |
string? |
"Key System" |
UI title |
GetKey |
(() -> ())? |
None | Runs (via task.spawn) when GET KEY is pressed |
Auth |
((string) -> any)? |
None | Validates the entered key; a truthy return means success. Without this, activation always fails |
OnSuccess |
(() -> ())? |
None | Runs after successful authentication |
Pressing ACTIVATE calls Auth(key) (wrapped in pcall, so a thrown error counts as failure) in a spawned task; on success it shows a success state, waits briefly, then runs OnSuccess and closes the UI. Auth must be a real Luau function — a C function is rejected at construction.
Returns { Close = closeFunction }. Close() animates the UI out and destroys it. Authentication UI is not tracked with normal windows — Library:Unload() does not close it; call Close() yourself.
element:Destroy()— removes that element and its UI, disconnecting its own connections.section:Destroy()— destroys its tracked elements, connections, and frame.window:Destroy()— destroys tracked tabs/sections, window-local toasts, and connections; closes any open color picker. If SimplismUI created the window'sScreenGui, thatScreenGuiis destroyed too; if you supplied your own viaParent, it's preserved.Library:Unload()— destroys every tracked window, the globalNotificationUI, clears the active-window list, and resets preset colors to SimplismUI's defaults. It does not reset theme colors set viaSetTheme, does not resetLibrary.Theme, and does not close authentication UI (use itsClose()).
local Library = require(script.Parent.SimplismUI)
local window = Library:Window({
Title = "SimplismUI",
TabBarLocation = "Left",
ToggleKeybind = Enum.KeyCode.RightShift,
})
local mainTab = window:AddTab({ Title = "Main", Columns = 3 })
local section = mainTab[1]:NewSection({ Title = "Settings" })
local selection = mainTab[2]:NewSection({ Title = "Selection" })
local actions = mainTab[3]:NewSection({ Title = "Actions" })
local statusLabel = section:AddLabel({ Text = "Status: Ready" })
section:AddToggle({
Text = "Enabled",
Default = false,
Callback = function(enabled)
statusLabel:SetText(enabled and "Status: Enabled" or "Status: Disabled")
end,
})
section:AddSlider({
Text = "Power",
Min = 0,
Max = 100,
Default = 50,
Increment = 5,
Callback = function(value)
print("Power:", value)
end,
})
selection:AddDropdown({
Text = "Mode",
Options = { "Normal", "Fast", "Precise" },
Searchable = true,
Callback = function(mode)
print("Mode:", mode)
end,
})
selection:AddOptionGrid({
Text = "Profile",
CellsInFillDirection = 3,
CellSizeY = UDim.new(0, 28),
Options = {
{ Name = "One" },
{ Name = "Two" },
{ Name = "Three" },
},
Callback = function(name)
print("Profile:", name)
end,
})
actions:AddButton({
Text = "Save",
Callback = function()
window:Toast({
Text = "Settings saved",
Type = "Success",
Position = "BottomRight",
})
end,
})Silent defaults. Dropdown, multi-select dropdown, and Option Grid defaults initialize their selections without invoking Callback. Slider defaults are also silent, including Default = 0/0 (NaN). User interactions and callback-firing programmatic setters retain their documented behavior; explicitly invoke your application logic if it must run once for initial values.
Slider NaN. Explicitly setting a slider to NaN (slider:SetValue(0/0)) puts it in a special state: GetValue() returns NaN, the display shows "nan", and the callback receives NaN. This is only reachable by deliberately passing NaN.
Multi-select internal table. GetSelected() returns the library's live internal table (the callback gets the same reference) — clone before mutating externally.
Preset table ownership. SetPresetColors stores your table by reference, and Library:Unload() clears and repopulates the active preset table with defaults — which mutates your table if you passed it directly. Pass table.clone(myPresets) if you need to keep your own copy independent.
Color picker switching quickly. Opening a new picker while another is closing can occasionally leave the shared dimming overlay hidden while the new picker is still open; the picker itself still works normally.
Duplicate dropdown option strings. Options are used as internal lookup keys — keep them unique, especially when using search, multi-select, or dynamic option changes.
Theme value types aren't validated. SetTheme checks for unknown keys but not that values are actually Color3s; bad values can cause errors later when a tween or property assignment runs.
AddOption in a loop is O(n²). Each AddOption call rebuilds the entire grid (destroying and recreating every existing cell), so adding options one at a time in a loop costs quadratic time as the list grows. Use AddOptions to add many options in one rebuild.