Skip to content

UI slots ​

Component positions in the host UI that plugins can replace. Declared in mod.json under ui_extensions.


Slot list ​

✅ Consumed (replaceable) ​

SlotPositionMutually exclusive
overlay_backgroundSubtitle overlay backplate✅
caption_viewCaption content view✅
audio_levelBottom-right level meter / waveform bar✅
alert_capsuleFloating alert capsule✅
overlay_decorationsOverlay decoration layer✅
style_bar_actionsCustom action buttons in the caption-bar control row❌
settings_backgroundSettings center backplate✅
history_backgroundHistory window backplate✅
dialog_backgroundGeneric dialog backplate✅
window_backgroundGeneric window backplate✅
copilot_windowAI assistant top-level floating window✅
settings_viewStandalone settings form for a plugin (special, not in the standard list, never exclusive)❌

❌ Declared but not consumed by any Loader today (do not rely on them) ​

  • overlay_root
  • style_bar

Mutual exclusion rules ​

  • Slots marked ✅ above are mutually exclusive per slot: only one plugin can be in effect in the same slot at a time.
  • Full themes (style_layer=full) and local UI slot extensions can coexist — enabling a waveform plugin does not take the user's theme offline.
  • Mutual exclusion between local widgets of the same class only happens when both are local (neither side is a full theme).
  • settings_view is never mutually exclusive.

Cascade fallback: a window looks for an available plugin background in the order dedicated slot → window_background → overlay_background; if it finds none it uses the official native backplate.


Declaration ​

json
{
  "type": "theme",
  "style_layer": "effects",
  "ui_extensions": {
    "audio_level": "CustomWaveform.qml",
    "overlay_background": "MyBackground.qml"
  }
}

Paths are relative to the plugin directory.


Background slot guidelines ​

If you declare any of overlay_background / settings_background / history_background / dialog_background / window_background, you must follow these rules:

1) Border drawing is your responsibility (you must draw it yourself) ​

When the host loads a plugin background it automatically hides the official native 1px crystal highlight outer border (to prevent double overlapping lines). So a plugin background must draw its own window outer edge / border / outer glow (two-tone neon gradient frame, chamfered metal, highlight stroke …). Filling a flat colour without drawing a border → the window becomes a bare, borderless rectangle.

2) Adaptive corner radius ​

The host injects the target window's native corner radius through customRadius (settings center 16, history window 16, generic dialog 12 …). A component should declare property bool enforceCustomRadius: false and adapt when customRadius changes; do not hard-code a tiny radius (such as customRadius: 2) — you get an abrupt, harsh clipped right angle.

3) Context property pass-through ​

PropertyContent
window / targetWindowTarget window instance (you can probe window.activeFocus / window.hovered)
appStateGlobal application state (appState.bgColor / bgOpacity / neonBorderColor …)
hostContextOverlay-window-specific context (style / subtitle / audio / invoke)
hoveredWhether the overlay window is currently hovered by the mouse

4) Live response to config values (the trap people hit most) ​

⚠️ bridge.getPluginConfig(pluginId) is a transient function call, not a reactive QML property. If you define switches such as neon_glow / hud_brackets in mod.json, you must listen to bridge.pluginConfigChanged, otherwise flipping a switch in the settings panel does nothing to a window that is already running.

qml
import QtQuick 2.15

Item {
    id: root
    anchors.fill: parent

    readonly property string myPluginId: "community.theme.my_theme"

    property var cfg: (typeof bridge !== "undefined" && bridge && bridge.getPluginConfig)
        ? bridge.getPluginConfig(root.myPluginId) : ({})

    readonly property bool enableGlow: (cfg && typeof cfg.neon_glow !== "undefined") ? cfg.neon_glow : true
    readonly property bool enableBrackets: (cfg && typeof cfg.hud_brackets !== "undefined") ? cfg.hud_brackets : true

    Connections {
        target: (typeof bridge !== "undefined") ? bridge : null
        function onPluginConfigChanged(pid) {
            if (pid === root.myPluginId) {
                root.cfg = bridge.getPluginConfig(root.myPluginId) || ({})
            }
        }
    }

    Rectangle {
        anchors.fill: parent
        visible: root.enableGlow
        border.color: "#00f0ff"
        border.width: 1.5
    }
}

Window material window_backdrop ​

Only at the top level of theme.json (writing it into mod.json is ignored): acrylic | mica | mica_alt | blur | none. See theme.


Reference implementations ​

DirectoryDemonstrates
extensions/example_theme_cyberpunk/full theme + 5 slots (including decorations)
extensions/community_theme_glassmorphism/a complete glass-texture background set
extensions/example_custom_waveform_ui/a local extension that only replaces audio_level (style_layer: effects)

TransBox — empowering borderless cross-language communication for everyone