Skip to main content

Theming Guide

Theming controls ImGui's global style — every window background, widget color, spacing value, and rounding radius across your entire application. This is different from per-widget styling where you set colors and vars on individual components.

XFrames ships with three built-in themes and supports runtime switching via patchStyle.

Built-in themes

ThemeStyleAccent colorBase
Dark (theme2)Dark backgrounds, light textGreen #75f986Black #0A0B0D
Light (theme1)Light backgrounds, dark textCoral #ff6e59White #fff
Ocean (theme3)Deep navy backgroundsTeal #56d6d6Navy #1a1b2e

Each theme defines a color palette and maps it onto ImGui's 53 color slots:

import { XFramesStyleForPatching, ImGuiCol } from "@xframes/common";

const oceanColors = {
deepNavy: "#1a1b2e",
navy: "#252641",
midNavy: "#2f3055",
slate: "#4a4b6a",
lightSlate: "#7a7b9a",
silver: "#b8bbd0",
offWhite: "#e8eaf6",
teal: "#56d6d6",
hoverTeal: "#7aeaea",
};

const oceanTheme: XFramesStyleForPatching = {
colors: {
[ImGuiCol.Text]: [oceanColors.offWhite, 1],
[ImGuiCol.WindowBg]: [oceanColors.deepNavy, 1],
[ImGuiCol.Button]: [oceanColors.midNavy, 1],
[ImGuiCol.ButtonHovered]: [oceanColors.slate, 1],
[ImGuiCol.ButtonActive]: [oceanColors.teal, 1],
// ... all 53 ImGuiCol entries
},
};

Theme object structure

Themes use the XFramesStyleForPatching type — a partial object where you only specify what you want to override:

type XFramesStyleForPatching = Partial<Omit<XFramesStyle, "colors">> & {
colors?: {
[k in ImGuiCol]?: HEXA;
};
};

type HEXA = [string, number]; // [hexColor, alpha 0.0–1.0]

The colors property maps ImGuiCol enum values to HEXA tuples. You can also override numeric style properties:

const theme: XFramesStyleForPatching = {
// Numeric style vars
windowRounding: 8,
frameRounding: 4,
framePadding: [6, 4], // [x, y]
itemSpacing: [8, 6],

// Colors
colors: {
[ImGuiCol.Text]: ["#ffffff", 1],
[ImGuiCol.WindowBg]: ["#1a1a2e", 1],
[ImGuiCol.FrameBg]: ["#16213e", 0.9], // 90% opaque
},
};

Setting the initial theme

Pass your theme as the 4th argument to render():

import { render } from "./lib/render";
import { theme2 } from "./themes";

render(App, assetsBasePath, fontDefs, theme2);

The theme is serialized to JSON and sent to the C++ core during initialization.

Runtime theme switching

Call patchStyle on the native module to change the theme at runtime. It lives directly on the native module — not on WidgetRegistrationService:

import { ComboChangeEvent, useWidgetRegistrationService } from "@xframes/common";
import { theme1, theme2, theme3 } from "../themes";

const themeList = [
{ name: "Dark", theme: theme2 },
{ name: "Light", theme: theme1 },
{ name: "Ocean", theme: theme3 },
];

function ThemeSwitcher() {
const widgetRegistrationService = useWidgetRegistrationService();

const handleThemeChange = useCallback((event: ComboChangeEvent) => {
const index = event.nativeEvent.value;
(widgetRegistrationService as any).wasmModule.patchStyle(
JSON.stringify(themeList[index].theme),
);
}, [widgetRegistrationService]);

return (
<XFrames.Combo
options={themeList.map((t) => t.name)}
initialSelectedIndex={0}
onChange={handleThemeChange}
/>
);
}

The style update takes effect on the next render frame — there is no flicker or delay.

Creating a custom theme

  1. Define your color palette as a plain object with descriptive names:
const myColors = {
bg: "#2b2d42",
surface: "#3a3d5c",
text: "#edf2f4",
accent: "#ef233c",
accentHover: "#ff4757",
muted: "#8d99ae",
};
  1. Create the theme object, mapping palette colors to ImGui color slots:
import { XFramesStyleForPatching, ImGuiCol } from "@xframes/common";

const myTheme: XFramesStyleForPatching = {
// Optional: override style vars
windowRounding: 6,
frameRounding: 4,
grabRounding: 4,

colors: {
// Text
[ImGuiCol.Text]: [myColors.text, 1],
[ImGuiCol.TextDisabled]: [myColors.muted, 1],

// Backgrounds
[ImGuiCol.WindowBg]: [myColors.bg, 1],
[ImGuiCol.ChildBg]: [myColors.bg, 1],
[ImGuiCol.PopupBg]: [myColors.surface, 1],

// Borders
[ImGuiCol.Border]: [myColors.muted, 1],
[ImGuiCol.BorderShadow]: [myColors.bg, 1],

// Frames (inputs, checkboxes, sliders)
[ImGuiCol.FrameBg]: [myColors.surface, 1],
[ImGuiCol.FrameBgHovered]: [myColors.muted, 0.4],
[ImGuiCol.FrameBgActive]: [myColors.muted, 0.6],

// Buttons
[ImGuiCol.Button]: [myColors.accent, 1],
[ImGuiCol.ButtonHovered]: [myColors.accentHover, 1],
[ImGuiCol.ButtonActive]: [myColors.bg, 1],

// Headers (collapsing headers, tree nodes, table headers)
[ImGuiCol.Header]: [myColors.surface, 1],
[ImGuiCol.HeaderHovered]: [myColors.muted, 0.5],
[ImGuiCol.HeaderActive]: [myColors.accent, 1],

// Tabs
[ImGuiCol.Tab]: [myColors.surface, 1],
[ImGuiCol.TabHovered]: [myColors.muted, 0.5],
[ImGuiCol.TabActive]: [myColors.accent, 1],
[ImGuiCol.TabUnfocused]: [myColors.surface, 1],
[ImGuiCol.TabUnfocusedActive]: [myColors.muted, 1],

// Tables
[ImGuiCol.TableHeaderBg]: [myColors.surface, 1],
[ImGuiCol.TableBorderStrong]: [myColors.muted, 1],
[ImGuiCol.TableBorderLight]: [myColors.surface, 1],
[ImGuiCol.TableRowBg]: [myColors.bg, 1],
[ImGuiCol.TableRowBgAlt]: [myColors.surface, 1],

// Interactive elements
[ImGuiCol.CheckMark]: [myColors.accent, 1],
[ImGuiCol.SliderGrab]: [myColors.accent, 1],
[ImGuiCol.SliderGrabActive]: [myColors.accentHover, 1],

// Scrollbars
[ImGuiCol.ScrollbarBg]: [myColors.bg, 1],
[ImGuiCol.ScrollbarGrab]: [myColors.muted, 1],
[ImGuiCol.ScrollbarGrabHovered]: [myColors.text, 0.5],
[ImGuiCol.ScrollbarGrabActive]: [myColors.text, 0.8],

// Misc
[ImGuiCol.Separator]: [myColors.muted, 1],
[ImGuiCol.TextSelectedBg]: [myColors.accent, 0.4],
[ImGuiCol.ModalWindowDimBg]: [myColors.bg, 0.7],
},
};
  1. Use it as the initial theme or switch to it at runtime:
// As initial theme
render(App, assetsBasePath, fontDefs, myTheme);

// Or switch at runtime
(widgetRegistrationService as any).wasmModule.patchStyle(
JSON.stringify(myTheme),
);
note

You don't have to set all 53 color slots. patchStyle only overrides the colors you specify — the rest keep their current values. However, for a complete theme (used as the initial theme), you should define all slots for a consistent look.

ImGuiCol reference

All 53 color IDs available in the ImGuiCol enum:

Text & backgrounds

EnumValueControls
Text0Default text color
TextDisabled1Disabled/greyed-out text
WindowBg2Window backgrounds
ChildBg3Child window backgrounds
PopupBg4Popup and tooltip backgrounds

Borders

EnumValueControls
Border5Border lines
BorderShadow6Border shadow (usually transparent)

Frames (inputs, checkboxes, sliders)

EnumValueControls
FrameBg7Input field background
FrameBgHovered8Input field background on hover
FrameBgActive9Input field background when active

Title bars

EnumValueControls
TitleBg10Unfocused title bar
TitleBgActive11Focused title bar
TitleBgCollapsed12Collapsed title bar
MenuBarBg13Menu bar background

Scrollbars

EnumValueControls
ScrollbarBg14Scrollbar track
ScrollbarGrab15Scrollbar thumb
ScrollbarGrabHovered16Scrollbar thumb on hover
ScrollbarGrabActive17Scrollbar thumb when dragging

Interactive elements

EnumValueControls
CheckMark18Checkbox check mark
SliderGrab19Slider handle
SliderGrabActive20Slider handle when dragging
Button21Button background
ButtonHovered22Button background on hover
ButtonActive23Button background when clicked

Headers & separators

EnumValueControls
Header24Collapsing header, tree node, table header
HeaderHovered25Header on hover
HeaderActive26Header when clicked
Separator27Separator line
SeparatorHovered28Separator on hover
SeparatorActive29Separator when dragging

Resize grips

EnumValueControls
ResizeGrip30Resize grip (bottom-right of windows)
ResizeGripHovered31Resize grip on hover
ResizeGripActive32Resize grip when dragging

Tabs

EnumValueControls
Tab33Unselected tab
TabHovered34Tab on hover
TabActive35Selected/active tab
TabUnfocused36Unselected tab in unfocused window
TabUnfocusedActive37Selected tab in unfocused window

Plots

EnumValueControls
PlotLines38Line plot default color
PlotLinesHovered39Line plot hover highlight
PlotHistogram40Histogram bar default color
PlotHistogramHovered41Histogram bar hover highlight

Tables

EnumValueControls
TableHeaderBg42Table header row background
TableBorderStrong43Outer and header borders
TableBorderLight44Inner column/row borders
TableRowBg45Row background (even rows)
TableRowBgAlt46Alternating row background (odd rows)

Selection & navigation

EnumValueControls
TextSelectedBg47Text selection highlight
DragDropTarget48Drag-and-drop target highlight
NavHighlight49Keyboard navigation highlight
NavWindowingHighlight50Window switching highlight
NavWindowingDimBg51Background dim during window switching
ModalWindowDimBg52Background dim behind modal windows

Next steps