Modal Dialogs & Overlays
Wijjit’s overlay system lets you layer modals, dropdowns, context menus, and tooltips above the base UI without rewriting your layout. Everything is coordinated by wijjit.core.overlay.OverlayManager.
Overlay layers
Overlays live in one of several wijjit.core.overlay.LayerType buckets:
BASE– normal UI (no overlay).MODAL– blocking dialogs, confirmation flows.DROPDOWN– dropdowns, context menus.TOOLTIP– transient tooltips/popovers.
Each layer has its own z-index range. Within a layer, newly pushed overlays get a higher z-index so they stack correctly.
Showing a modal from a template
The simplest way to show a modal is the {% modal %} tag bound to a state key
via visible=. The modal renders only while that state key is truthy, and you
flip it from action handlers:
from wijjit import Wijjit, render_template_string
app = Wijjit()
app.state["show_confirm"] = False
@app.view("home", default=True)
def home():
return render_template_string("""
{% vstack %}
{% button action="open" %}Delete project{% endbutton %}
{% modal id="confirm" visible="show_confirm" title="Delete project?" width=44 height=9 %}
This cannot be undone.
{% hstack spacing=2 %}
{% button action="confirm_cancel" %}Cancel{% endbutton %}
{% button action="confirm_delete" variant="danger" %}Delete{% endbutton %}
{% endhstack %}
{% endmodal %}
{% endvstack %}
""")
@app.on_action("open")
def open_modal(event):
app.state["show_confirm"] = True
@app.on_action("confirm_cancel")
def cancel(event):
app.state["show_confirm"] = False
@app.on_action("confirm_delete")
def delete(event):
# ... perform deletion ...
app.state["show_confirm"] = False
The {% modal %} tag accepts id, visible (state key for visibility),
title, width, height, and border (single, double,
rounded). Focus trapping and a dimmed background are applied automatically
for modal-layer overlays.
Creating overlays programmatically
The app exposes one-call helpers that build the overlay entry for you and return
the Overlay handle:
app.show_modal(element, on_close=None, dim_background=True, close_on_escape=True, close_on_click_outside=False)– focus-trapping modal on theMODALlayer.app.show_dropdown(element, x, y, on_close=None, close_on_click_outside=True, close_on_escape=True)– positioned dropdown on theDROPDOWNlayer.app.show_tooltip(element, x, y, close_on_click_outside=True)– transient tooltip on theTOOLTIPlayer (see Mouse Support).
from wijjit.elements.display.modal import ModalElement
def show_confirm():
modal = ModalElement(title="Delete project?", width=44, height=9, border_style="rounded")
overlay = app.show_modal(modal, on_close=lambda: app.state.update({"dirty": False}))
For full control — custom layer, z-index, or dim behaviour — you can instead
instantiate a wijjit.elements.display.modal.ModalElement and push it onto
the overlay manager yourself:
from wijjit.core.overlay import LayerType
from wijjit.elements.display.modal import ModalElement
def show_confirm():
modal = ModalElement(
title="Delete project?",
width=44,
height=9,
border_style="rounded",
)
app.overlay_manager.push(
modal,
layer_type=LayerType.MODAL,
trap_focus=True,
dimmed_background=True,
)
ModalElement itself is just a bordered container - its constructor params are
id, title, width, height, border_style, centered, and
padding (there is no body or buttons argument; add child elements via
the layout tree, or prefer the {% modal %} template approach above for
button-bearing dialogs).
OverlayManager.push returns an Overlay object you can keep to close manually. Options:
close_on_click_outside– close if the user clicks outside the overlay (defaultTrue).close_on_escape– respond to Escape (defaultTrue).trap_focus– keep focus inside the overlay until closed (setTruefor modals).dimmed_background– render a dimmed layer behind the overlay.on_close– callback invoked after the overlay is dismissed.
When trap_focus=True, keyboard navigation (Tab/Shift+Tab) continues to work within the modal, cycling through its focusable elements. Focus is automatically restored to the previously focused element when the modal closes.
A trapping overlay always takes focus somewhere, defaulting to its first focusable element. Mark an element inside the dialog autofocus=True to choose a different one - useful for putting the cursor straight into a TextInputDialog-style prompt, or on the safe button of a destructive confirmation. See Focus Navigation for details.
Templated dialogs
You rarely need to instantiate modals manually. wijjit.tags.dialogs exposes tags tailored to common flows:
{% confirmdialog %}Renders a confirm/cancel dialog. Attributes:
id,visible(state key controlling visibility - required for the dialog to appear),title,message(or supply the message as body content),confirm_action,cancel_action,confirm_label,cancel_label,width,height,border.{% confirmdialog visible="show_confirm" title="Delete file?" message="This cannot be undone." confirm_action="do_delete" cancel_action="cancel_delete" confirm_label="Delete" cancel_label="Cancel" %}{% endconfirmdialog %}
{% alertdialog %}Simple “OK” dialog. Attributes:
id,visible(required state key),title,message(or body content),ok_action,ok_label,width,height,border.{% inputdialog %}Collects a short text input. Attributes:
id,visible(required state key),title,prompt,initial_value,submit_action,cancel_action,placeholder,submit_label,cancel_label,width,height,border,input_width.
Each dialog tag is only rendered while its visible state key is truthy, so set that key to True from an action handler to show the dialog and back to False to dismiss it. The renderer turns each tag into the matching dialog overlay element and pushes it automatically. Actions fire like any other button: use @app.on_action("do_delete") to handle the user’s choice.
Closing overlays
Ways an overlay can close:
User clicks outside (
close_on_click_outside).User presses Escape (
close_on_escape).An action handler calls
app.close_overlay(overlay)— the public counterpart to theshow_*helpers — to dismiss the overlay it returned (orapp.close_overlay()with no argument to close the topmost). For lower-level control,app.overlay_manager.pop(overlay)closes one overlay andapp.overlay_manager.pop_layer(LayerType.MODAL)closes every overlay in a layer.Overlays tied to state (e.g., notifications) update their
visible_state_keytoFalse.
When closing, the overlay manager restores the previous focus state so the user returns to the element they were interacting with before the modal opened.
Stacking & multiple overlays
It’s common to have a dropdown inside a modal or a tooltip above a dropdown. Because each layer has its own z-index, you can safely push overlays in any order. Best practices:
Keep the overlay stack shallow; nested modals are confusing.
Always supply
trap_focus=Truefor modals so keyboard users don’t accidentally interact with the background.Use
dimmed_backgroundsparingly (only for blocking dialogs).
Notifications & toasts
Toast-style notifications also use the overlay system under the hood. The
easiest entry point is the high-level app.notify() helper, which builds a
notification element and queues it for you:
app.notify("Saved successfully", severity="success")
app.notify("Deployment failed", severity="error", duration=None) # no auto-dismiss
severity is one of "info" (default), "success", "warning", or
"error". Pass duration=None for a notification that stays until
dismissed, and an optional action=(label, callback) tuple to add an action
button. notify() returns a notification id you can pass to
app.dismiss_notification(id).
For lower-level control, wijjit.core.notification_manager.NotificationManager
exposes add(element, duration=3.0, on_close=None), where element is a
wijjit.elements.display.notification.NotificationElement:
from wijjit.elements.display.notification import NotificationElement
note = NotificationElement(message="Saved successfully", severity="success")
app.notification_manager.add(note, duration=3.0)
The manager creates overlay entries at the edge of the viewport and removes them when timers expire or the user dismisses them.
Tips
Prepare dialog templates as macros for reuse (e.g.,
{% macro confirm_delete(name) %}…{% endmacro %}).Centralize overlay actions (
confirm_delete,confirm_cancel) in a dedicated module so business logic isn’t scattered across views.Consider storing overlay visibility in state (
state.show_settings_modal) if the dialog affects layout or needs to coordinate with other components.
For focus-specific guidance read Focus Navigation; for pointer interactions see Mouse Support.