Skip to content
github-actions[bot] edited this page Jun 16, 2026 · 3 revisions

Contents

The UI API renders the settings center for an app registered through the Config API. It owns the visible frame, navigation, dashboard, sidebar, search, page layout, control widgets, notes, density controls, reload-pending state display hooks, topbar actions, side-panel subnavigation, and open/toggle helpers.

It should render metadata. It should not contain host-addon-specific feature logic.

local addonName, addon = ...
local ConfigUI = addon.LibSettingsDesigner and addon.LibSettingsDesigner.UI

The UI API expects addon.LibSettingsDesigner.Config to be loaded first.

Method Description
Open(appOrID, pageID, focusControlID) Opens the settings frame. Optionally navigates to a page and focuses a control.
Toggle(appOrID, pageID, focusControlID) Opens the frame if hidden, or hides it if already open.
GetFrame(appOrID) Returns the existing settings frame for the app, when available.
ResolveOpenTarget(app, pageID, focusControlID) Resolves page/control focus shortcuts.
ConfigUI:Open(app)
ConfigUI:Open(app, "interface.action-bars")
ConfigUI:Open(app, "interface.action-bars", "barScale")

appOrID can be the app object or the registered addon id:

ConfigUI:Open("MyAddon", "general.core")

Use stable page/control ids. Do not use localized labels.

Good:

ConfigUI:Open(app, "interface.action-bars", "barScale")

Bad:

ConfigUI:Open(app, "Action Bars", "Scale")
ConfigUI:Toggle(app)
ConfigUI:Toggle(app, "general.core")

Use Toggle for slash commands or minimap buttons that should close the window when it is already open.

Use Open when the user explicitly clicked a navigation target and you always want the requested page shown.

local frame = ConfigUI:GetFrame(app)

Use this when a host addon needs to check whether the settings center already exists or is currently shown.

Example:

local frame = ConfigUI:GetFrame(app)
if frame and frame:IsShown() then
  -- Settings window is already open.
end

ResolveOpenTarget(app, pageID, focusControlID) resolves page/control shortcuts used by Open.

A direct page and control id is clearest:

ConfigUI:Open(app, "interface.action-bars", "barScale")

If a combined target is used, the resolver can split the longest matching page id and use the remaining part as the control id. Keep ids unambiguous.

The rendered frame stores internal state at:

frame._LibSettingsDesignerState

Use this only for narrow integration points, such as refreshing the currently rendered page after external runtime data changed:

local frame = addon.ConfigCenterFrame
local state = frame and frame._LibSettingsDesignerState
if frame and frame:IsShown() and state and state.RenderContent then
  state:RenderContent()
end

Do not use frame state as a replacement for proper setters, wrapper callbacks, or control registration.

The app can provide persistence callbacks:

local app = Config:RegisterAddOn(addonName, {
  getSize = function()
    local db = MyAddonDB.profile.settingsWindow or {}
    return db.width, db.height
  end,
  setSize = function(width, height)
    MyAddonDB.profile.settingsWindow = MyAddonDB.profile.settingsWindow or {}
    MyAddonDB.profile.settingsWindow.width = width
    MyAddonDB.profile.settingsWindow.height = height
  end,
  getLocked = function()
    return MyAddonDB.profile.settingsWindow
      and MyAddonDB.profile.settingsWindow.locked == true
  end,
  setLocked = function(locked)
    MyAddonDB.profile.settingsWindow = MyAddonDB.profile.settingsWindow or {}
    MyAddonDB.profile.settingsWindow.locked = locked == true
  end,
  getDensity = function()
    return MyAddonDB.profile.settingsWindow
      and MyAddonDB.profile.settingsWindow.density
  end,
  setDensity = function(value)
    MyAddonDB.profile.settingsWindow = MyAddonDB.profile.settingsWindow or {}
    MyAddonDB.profile.settingsWindow.density = value == "compact" and "compact" or "comfortable"
  end,
})

Use these callbacks instead of hard-coding frame size, lock, or density state inside the library. density sets the initial layout; getDensity and setDensity persist the user's compact/comfortable choice. Set showDensityButton = false when the density switch should not be shown.

The app can configure built-in topbar parts and add action buttons without manual frame positioning:

local app = Config:RegisterAddOn(addonName, {
  topbar = {
    showSearch = true,
    showDensity = true,
    showLock = true,
    showDefaults = true,

    titleActions = {
      {
        id = "reload",
        label = RELOADUI or "Reload UI",
        visible = function(app) return app:IsReloadPending() end,
        tooltip = function(app) return app:GetReloadPendingReason() end,
        pulse = true,
        onClick = function() ReloadUI() end,
      },
    },

    actions = {
      {
        id = "tools",
        icon = "Interface\\Icons\\INV_Misc_Gear_01",
        iconOnly = true,
        tooltip = "Tools",
        menu = {
          { text = "Open profile folder", onClick = function() MyAddon.ShowProfilePath() end },
          { text = "Reset cache", onClick = function() MyAddon.ResetCache() end },
        },
      },
    },
  },
})

titleActions render after the title from left to right. actions / rightActions render automatically to the left of the built-in search, lock, density, and defaults controls. The library owns spacing, button chrome, hover state, pulse state, and context-menu creation.

Action fields:

Field Purpose
id Stable action id.
label / text / title Button text. May be a function.
icon / iconKey Optional icon texture or app icon-key lookup.
iconOnly true makes a compact icon button.
visible / visibleWhen / isVisible Show gate. May be a function.
enabled / enabledWhen / isEnabled Enabled gate. May be a function.
tooltip / description Tooltip text. May be a function.
pulse true applies a subtle alpha pulse.
onClick(app, action, state, button) Click callback for normal actions.
menu / menuItems / entries Static context-menu entries.
buildMenu / setupMenu Callback for custom MenuUtil.CreateContextMenu setup.

Menu entries support text/label, onClick, checked/isSelected, setSelected, nested children/entries, visible/isVisible, and divider = true.

Reload prompts remain host-owned. A host addon can mark reload-pending via requiresReload = true on controls or app:MarkReloadPending(reason), then show that state through a topbar action as above.

app:RegisterControl("interface.names", {
  id = "nameplateFont",
  key = "nameplateFont",
  type = "dropdown",
  label = "Nameplate font",
  default = "default",
  list = fontOptions,
  orderList = fontOrder,
  requiresReload = true,
  reloadReason = "Nameplate font changes apply after reloading the UI.",
})

On detail pages with a right side panel, the library can render optional links to page groups. The default is off: existing pages do not show a Sections block unless the host addon enables it. When enabled for a normal settings page with more than one visible group, the right panel shows a Sections block below the page about text. Clicking a section opens the same page, expands that group if needed, and scrolls to the first control in the group.

Side panel subnavigation example

Enable it globally on app options or for one page:

local app = Config:RegisterAddOn(addonName, {
  subnav = {
    enabled = true,
  },
})

app:RegisterPage({
  id = "interface.minimap",
  category = "interface",
  title = "Minimap",
  showSubnav = true,
})

Then register page groups and assign controls with groupID. Each visible group becomes one button in the side-panel Sections block:

app:RegisterCategory({
  id = "interface",
  title = "Interface",
  order = 100,
})

app:RegisterPage({
  id = "interface.action-bars",
  category = "interface",
  title = "Action Bars & Buttons",
  description = "Configure action bar visibility and button labels.",
  showSubnav = true,
  order = 100,
})

app:RegisterGroup("interface.action-bars", {
  id = "visibility",
  title = "Visibility rules",
  order = 100,
})

app:RegisterGroup("interface.action-bars", {
  id = "growth",
  title = "Button growth",
  order = 200,
})

app:RegisterGroup("interface.action-bars", {
  id = "text",
  title = "Button text",
  order = 300,
})

app:RegisterControl("interface.action-bars", {
  id = "bar1Visibility",
  key = "bar1Visibility",
  type = "dropdown",
  label = "Action Bar",
  groupID = "visibility",
  default = "mouseover",
  list = {
    always = "Always",
    mouseover = "Mouseover",
    never = "Never",
  },
  orderList = { "always", "mouseover", "never" },
})

app:RegisterControl("interface.action-bars", {
  id = "buttonGrowth",
  key = "buttonGrowth",
  type = "dropdown",
  label = "Growth direction",
  groupID = "growth",
  default = "right",
  list = {
    left = "Left",
    right = "Right",
    up = "Up",
    down = "Down",
  },
  orderList = { "left", "right", "up", "down" },
})

app:RegisterControl("interface.action-bars", {
  id = "macroText",
  key = "macroText",
  type = "toggle",
  label = "Macro text",
  groupID = "text",
  default = true,
})

Use showSubnav = true, showSubnavigation = true, or subnav = { enabled = true } to show it for a page. Function values are called with the app/page context and must return true to show the block.

Use a current-page refresh when external state changes the options or visibility of already-rendered rows:

local frame = addon.ConfigCenterFrame
local state = frame and frame._LibSettingsDesignerState
if frame and frame:IsShown() and state and state.RenderContent then
  state:RenderContent()
end

Good use cases:

  • a standalone editor changed data used by visible rows
  • available dropdown options changed outside the dropdown interaction
  • a page-level visibility gate changed outside a settings callback

Avoid this:

  • inside a MultiDropdown option click
  • while a dropdown menu is open
  • as a substitute for a focused row update or runtime feature refresh

Open from a slash command:

SLASH_MYADDON1 = "/myaddon"
SlashCmdList.MYADDON = function(input)
  input = strtrim(input or "")
  if input == "bars" then
    ConfigUI:Open(app, "interface.action-bars")
  else
    ConfigUI:Toggle(app)
  end
end

Open a specific control from a warning prompt:

MyAddon.ShowFixButton = function()
  ConfigUI:Open(app, "general.core", "enabled")
end

Refresh after external data import:

MyAddon.ImportProfile = function(imported)
  MyAddonDB.profile = imported

  local frame = addon.ConfigCenterFrame
  local state = frame and frame._LibSettingsDesignerState
  if frame and frame:IsShown() and state and state.RenderContent then
    state:RenderContent()
  end
end

Wiki
  • Home
  • Architecture
  • Vendoring
  • Quick Start
  • Field Glossary
  • Troubleshooting
  • Validation

Reference
  ⚬ Config API
  ⚬ UI API
  ⚬ Elements
  ⚬ Examples

Elements
  Structure
   • Category
   • Page
   • Group
   • Dashboard
   • InfoPage
   • Custom
  Controls
   • Toggle
   • CheckboxDropdown
   • Dropdown
   • MultiDropdown
   • SoundDropdown
   • Input
   • Slider
   • Button
  Advanced
   • ColorPicker
   • ColorPalette
   • ColorOverrides
   • ReorderList
   • Expandable
   • Notes

Examples
  Start
   • Minimal Addon
   • Complete Settings Center
   • Wrapper Bridge Pattern
  Data and Behavior
   • Dependent Controls
   • Nested Database Values
   • Dynamic Dropdowns
   • Runtime Refresh
   • Search and New Badges
   • Custom Hosted Editors
  Polish
   • Support Links
   • Theme Colors
   • Theme Borders

Clone this wiki locally