Slots & UI
Plugins render into predefined slot regions within the editor workspace. This page details all available slots, visibility policies, Z-index conventions, and the contribution system.
Available Slots
| Slot Constant | Location / Context | Typical Plugins / Purpose |
|---|---|---|
SIDE_BAR |
Sidebar drawers (left & right sides) | Crop, Adjustment, Brush, Text, Image Info, AI Bridge |
OPTION_BAR |
Top strip (below header) | Tool-specific option panels (aspect ratio, zoom) |
DOCK |
Bottom tab panel area | Layer panel, History panel, Timeline |
TL / TR / BL / BR |
Viewport corner widgets | Anchored viewport HUD actions and status widgets |
VIEWPORT_OVERLAY |
Over the canvas (pointer-interactive) | Crop handles, selection outline, interactive frames |
STAGE_OVERLAY |
Stage-level layer (non-interactive) | Pixel grid, rulers, snapping guides |
STAGE_GIZMOS |
Interactive transform & crop tools | Free transform handles, crop boundaries |
ROOT_OVERLAY |
App-level absolute layers | Global notifications, dialogs, loading spinner overlays |
TOOL_MENU |
Left vertical toolbar | Cursor interaction tools selection menu |
HIDDEN |
No visual UI | TimeTraveler, Clipboard service, background hooks |
Show Policies
The show property determines when a plugin's UI is rendered:
| Policy | Behavior |
|---|---|
'always-show' |
Visible whenever the editor is open (regardless of document state) |
'frame-required' |
Visible only when at least one frame/document is loaded |
Most tools use 'frame-required' because they operate on layer data that doesn't exist without a document.
Z-Index Protocol
The workspace defines a strict Z-index map in EDITOR_Z_INDEX (@opengpex/editor/core/helpers/config) to prevent overlap conflicts:
| Z-Index | Layer | Content |
|---|---|---|
| 0 | STAGE.BACKDROP |
Stage backdrop |
| 100 | STAGE.CONTENT |
Layer stack starting level |
| 1000 | STAGE.GIZMOS |
Basic interactive entities (guide lines, anchor points) |
| 2000 | STAGE.SYSTEM_TOOLS |
Core tools (selection box, crop box) |
| 3000 | STAGE.DEVELOPER_ZONE |
Third-party safety zone for plugin gizmos |
| 2000 | UI.WORKSPACE_BASE |
Sidebar, top bar, action bar |
| 4000 | UI.OVERLAY |
Global overlay plugins |
| 5000 | UI.POPOVER |
Popover, dropdown (requires Portal) |
| 6000 | UI.MODAL |
Modal dialogs and warnings |
| 10100 | UI.TOOLTIP |
Tooltips — must float above Popover |
Within each slot, plugins are ordered by the order property (lower = rendered first / further back).
Sidebar Drawer Behavior
For SIDE_BAR plugins:
- Clicking a sidebar icon toggles its drawer panel; double-clicking isolates the drawer (closes all others)
- Multiple drawers can be open concurrently across Left/Right columns
- The
sideproperty ('left'|'right') determines default placement - Users can drag-and-drop sidebar icons to reorder or swap between columns
- Drawer panel width is set via
initialConfig.preferredWidth(in pixels); it defaults to 320 when unset
Drawer Auto-Reveal
SIDE_BAR plugins may declare an autoReveal rule to open automatically when editor state matches a condition:
export const plugin: EditorPlugin = {
// ...
autoReveal: {
// Pure, O(1) predicate — expansion triggers only on the false → true edge
when: (state) => !!state.activeFrameId,
// What happens when `when` becomes false:
// 'sticky' (default) — stay open until the user closes it
// 'restore' — restore the open state from before activation
// 'collapse' — always collapse
collapseWhenFalse: 'restore',
priority: 0, // higher wins when multiple drawers reveal simultaneously
},
};
Contributions System
Contributions System
Plugins can donate additional UI to external slots via the contributions array:
export const plugin: EditorPlugin = {
// ...
contributions: [
{
slot: 'OPTION_BAR',
component: MyOptionsBarWidget,
},
],
};
This allows a SIDE_BAR plugin to also contribute an options strip to the top bar without creating a separate plugin.
Each UIContribution accepts slot, component, plus optional order, group, title, and icon. Rendering conventions handled automatically by PluginSlot:
OPTION_BARentries are wrapped in a pill-shaped container (h-[34px] px-3.5, panel background with backdrop blur, rounded on first/last siblings) — keep your component content-only and let the wrapper provide the chrome- A divider is rendered automatically whenever
ordercrosses a multiple of 100 (full-width line inSIDE_BAR, short centered line inTOOL_MENU, none inOPTION_BAR)
Next Steps
- Plugin System Overview — Architecture context
- Your First Plugin — See slots in practice
- Plugin API Reference — Hook documentation
Last updated: 2026-07-30