Repository navigation
tabs
Styled tab bars with badge indicators, disabled tabs, programmatic selection, and badge visual customization.
local wu = GetMod("WindowUtils")
local tabs = wu.Tabs
tabs.bar("myTabs", {
{ label = "General", content = function()
ImGui.Text("General settings here")
end },
{ label = "Advanced", content = function()
ImGui.Text("Advanced settings here")
end },
})Render a tab bar with content callbacks.
| Parameter | Type | Default | Description |
|---|---|---|---|
| id | string | - | Unique tab bar ID |
| tabDefs | table | - | Array of tab definitions (see below) |
| opts.flags | number | 0 | ImGuiTabBarFlags |
Returns: two values, selected (number, 1-based active tab index) and changed (boolean, true on any frame where the active index differs from the previous frame).
When tabDefs is nil or empty, bar renders nothing and returns 0, false. Note that getSelected(id) returns 1 in that same situation, because it defaults to the first tab rather than reporting "no tabs". Only bar uses 0.
changed is not strictly "the user clicked": it is also true on the frame a select() request lands, and on any frame where the active index moves for some other reason (a tab being removed, for instance).
Each entry in tabDefs is a table:
| Field | Type | Default | Description |
|---|---|---|---|
| label | string | - | Tab label text |
| content | function|nil | nil | Callback to render tab content |
| badge | boolean|number|nil | nil | Badge indicator (see below) |
| badgeStyle | table|nil | nil | Per-tab badge visual overrides |
| disabled | boolean | false | Grey out, prevent selection, and skip the content callback |
| tooltip | string|nil | nil | Tooltip on tab hover |
| noScroll | boolean | false | Wrap content in a no-scroll child that fills available space |
A disabled tab renders no content at all, even when it is the selected tab (for example if it became disabled while active). Its content callback is skipped entirely, so nothing from it stays interactive.
-
badge = true- small green dot indicator -
badge = 3- red circle with number "3" (notification count) -
badge = nil- no badge -
badge = 0,badge = false, or any negative number - no badge, so you can pass a raw counter without guarding it -
disabled = truesuppresses the badge as well as the interaction
Override global badge appearance for a specific tab. badgeStyle only affects numeric badges; the badge = true dot is always green and ignores it.
{
label = "Alerts",
badge = 5,
badgeStyle = {
color = { 1.0, 0.5, 0.0, 0.9 }, -- orange, 90% opacity
fontScale = 0.8,
offset = 1,
},
content = drawAlerts,
}Programmatically select a tab by 1-based index. Takes effect on the next frame the bar renders. Works before the bar has ever rendered (state is created on demand). The index is not validated: an out-of-range value is consumed and nothing changes.
Get the currently selected tab index.
Returns: number - 1-based index, or 1 if the id has never rendered, has never been selected, or was destroyed. It never returns 0 or nil, so it cannot tell you "no tabs"; bar returns 0 for that case.
Register a callback invoked when any tab bar's active index changes, including changes caused by select(). The callback receives (barId, newIndex).
There is one global callback, not one per bar, so a second call replaces the first. Pass nil to clear.
| Parameter | Type | Description |
|---|---|---|
| fn | function|nil | Callback (barId, newIndex), or nil to clear |
Configure badge visual defaults. Affects all badges that don't have per-tab badgeStyle overrides.
| Parameter | Type | Description |
|---|---|---|
| opts.badgeConfig | table|nil | Badge visual configuration |
| Option | Type | Default | Description |
|---|---|---|---|
| color | table | red | RGBA table for circle fill (alpha controls opacity, e.g. {1, 0, 0, 0.8}). Text color auto-switches between white and black based on luminance. |
| fontScale | number | 0.8 | Font size multiplier (base 36px, keep at or below 1.0 to avoid blur) |
| offset | number | -5 | Badge offset from tab corner in pixels (negative = closer to corner) |
tabs.configure({
badgeConfig = {
fontScale = 0.7,
color = { 0.2, 0.6, 1.0, 0.85 }, -- blue with 85% opacity
offset = 2,
}
})Returns: table - current badge config state (for inspection)
This is the module's live config table, not a copy. Writing to it changes the global badge defaults without going through configure(), which means the cached badge colors are not invalidated and your new color will not appear until something else triggers a rebuild. Read it, do not mutate it.
Remove internal state for a tab bar ID. Call when dynamically created tab bars are no longer needed. Any pending select() is dropped and getSelected(id) goes back to reporting 1.
tabs.bar("settings", {
{ label = "General", content = drawGeneral },
{ label = "Notifications", badge = unreadCount, content = drawNotifications },
{ label = "Debug", disabled = not debugMode, tooltip = "Enable debug mode first",
content = drawDebug },
{ label = "Status", badge = isConnected, content = drawStatus },
})local selected, changed = tabs.bar("settings", {
{ label = "General", content = drawGeneral },
{ label = "Inbox", badge = unreadCount > 0 and unreadCount or nil, content = drawInbox },
})
if changed and selected == 2 then
unreadCount = 0
end-- Global badge style
tabs.configure({
badgeConfig = {
color = { 1.0, 0.4, 0.0, 0.9 }, -- orange, 90% opacity
fontScale = 0.7,
}
})
-- Per-tab override (this tab gets blue badges)
tabs.bar("main", {
{ label = "Chat", badge = msgCount, badgeStyle = {
color = { 0.2, 0.5, 1.0, 1.0 },
}, content = drawChat },
{ label = "Alerts", badge = alertCount, content = drawAlerts }, -- uses global orange
})-- Bridge tab selection to notification clearing
tabs.setOnSelect(function(barId, newIndex)
if barId == "##main_tabs" and newIndex == 2 then
wu.Notify.clearCategory("inbox")
end
end)-- Switch to tab 2 when a condition is met
if shouldShowAdvanced then
tabs.select("settings", 2)
end
-- Read which tab is active
local current = tabs.getSelected("settings")tabs.bar("fitted", tabDefs, {
flags = ImGuiTabBarFlags.FittingPolicyResizeDown
})