Center-overlay panel
Build a modal-style center-overlay panel in Imperal Cloud: declare center_overlay on the panel and open it from the sidebar for a focused full-bleed editor.
Declare center_overlay=True — read before building
Center-overlay rendering is activated declaratively by center_overlay=True on the @ext.panel decorator (v4.1.8+). You do not need a special panel_id, and you do not need any frontend change — set center_overlay=True and the host renders the panel as a full-bleed overlay over the main content area when it is dispatched.
slot="center" describes which slot the panel belongs to, but the overlay behavior itself comes from the center_overlay=True flag. The panel handler is reached only when an explicit ui.Call or auto_action dispatches it (center-overlay panels are not called during batch discovery), so pick any panel_id you like and trigger it from a sidebar ui.Call/auto_action.
When you want a focused, full-bleed editor that appears over the main content area, declare center_overlay=True on your @ext.panel and use auto_action on the sidebar root to open it immediately after discovery. The panel_id can be any name you choose — in this example we use "editor" to mirror the Notes extension, but the overlay behavior is driven by the center_overlay=True declaration, not by a specific id.
from __future__ import annotations
from imperal_sdk import Extension, ui
ext = Extension(
"my-ext",
display_name="My Extension",
description="An extension with a full-bleed center-overlay editor.",
actions_explicit=True,
)
# ── Inline stub data — replace with your real data fetch in production ─────
_NOTES = [
{"id": "note-1", "title": "First note", "body": "Body of note one."},
{"id": "note-2", "title": "Second note", "body": "Body of note two."},
]
def _get_note(note_id: str) -> dict | None:
return next((n for n in _NOTES if n["id"] == note_id), None)
# ── Left sidebar — discovery-safe, sets auto_action to open overlay ────────
@ext.panel(
"sidebar",
slot="left",
title="Notes",
icon="🗒️",
default_width=280,
min_width=200,
max_width=500,
)
async def sidebar(
ctx: object,
active_note_id: str = "",
view: str = "notes",
**kwargs: object,
) -> object:
notes = _NOTES # replace with: notes = await fetch_notes(ctx)
items = [
ui.ListItem(
id=note["id"],
title=note["title"],
selected=(note["id"] == active_note_id),
# Dispatching the "editor" panel (declared with center_overlay=True)
# opens it as a full-bleed center overlay over the main content area.
on_click=ui.Call("__panel__editor", note_id=note["id"]),
)
for note in notes
]
root = ui.Stack(
children=[ui.List(items=items)],
gap=2,
)
# auto_action: open the most-recent note on first load.
# Conditional: only when no note is already active and we are in the
# normal view (not trash). The host fires this once after discovery
# (the discovery-once guard). Navigating away and returning to
# the same extension page does NOT re-fire auto_action within the
# same session.
if notes and not active_note_id and view != "trash":
root.props["auto_action"] = ui.Call(
"__panel__editor", note_id=notes[0]["id"]
)
return root
# ── Center-overlay editor ──────────────────────────────────────────────────
# center_overlay=True is what makes this panel render as a full-bleed overlay
# (v4.1.8+). The panel_id can be any name you choose; here it is "editor".
# This handler is only reached via an explicit ui.Call or auto_action — center
# panels are not called during batch discovery.
@ext.panel(
"editor",
slot="center",
center_overlay=True,
title="Editor",
icon="📝",
)
async def editor(ctx: object, note_id: str = "", **kwargs: object) -> object:
# At batch discovery the center panel is NOT called (only left/right are
# called at init). This handler is only reached via an explicit
# ui.Call or auto_action. When called without note_id, return ui.Empty()
# rather than None — ui.Empty() is the correct "no content yet" signal.
if not note_id:
return ui.Empty(message="Select a note to edit", icon="📄")
note = _get_note(note_id)
if note is None:
return ui.Error(message="Note not found")
return ui.Stack(
children=[
ui.Text(note["title"], variant="h2"),
ui.RichEditor(
content=note["body"],
on_change=ui.Call("save_note", note_id=note_id),
),
],
gap=4,
)Walk-through
What makes this panel a center overlay?
The center_overlay=True flag on the @ext.panel decorator (v4.1.8+). When that flag is set, the host renders the panel as a full-bleed overlay over the main content area whenever it is dispatched via ui.Call or auto_action. The panel_id is just a name you choose — "editor" here mirrors the Notes extension, but any id works. You do not need a frontend change to register a new center-overlay panel; declaring center_overlay=True is enough.
Why does the center panel handler never see batch-discovery calls?
Batch discovery only calls the panels registered in config.panels.left and config.panels.right. Center-overlay panels are not in that config. The editor handler is only reached when an explicit ui.Call or auto_action dispatches it. This means you do not need a return None guard for discovery — but you do need the if not note_id: return ui.Empty() guard for the case where auto_action or a ui.Call arrives without a valid note_id.
Why auto_action on the sidebar root and not on the editor panel itself?
The host reads auto_action only from leftPanel.props.auto_action. Setting it anywhere else — on the editor's own root, on a right-panel root, on a child node — has no effect. The sidebar owns the auto_action prop; the editor is the target of the action.
The three-condition guard for auto_action.
if notes and not active_note_id and view != "trash" mirrors the production pattern from notes/panels.py. The conditions are: (1) there is content to open — do not claim center for an empty list; (2) no note is already active — avoid re-claiming center when the sidebar refreshes while an editor is open; (3) not in the trash view — the trash view has different semantics and should not auto-open a note.
Cross-links
- Build a panel layout — center-overlay-editor — full guide including the Vikunja debugging walkthrough.
- Troubleshoot: center overlay does not open — step-by-step diagnostic for the most common center-overlay failure.
- Concepts: slot ownership — explains why
slot=is not the routing mechanism.
Master-detail panel
Build a master-detail panel in Imperal Cloud: a list on the left drives an editor in the center via ui.Call, with the newest item auto-opened on first load.
Auto-open the most-recent item on panel mount
Auto-open the most-recent item when an Imperal Cloud panel mounts: set auto_action on the left panel root and the host fires it once, no user click required.