Widgets
Modal
An overlay container for dialogs and focused interactions.
An overlay container for dialogs and focused interactions.
Usage
ui.layers([
MainContent(),
state.showModal &&
ui.modal({
id: "confirm",
title: "Confirm Action",
content: ui.text("Are you sure?"),
actions: [
ui.button({ id: "yes", label: "Yes" }),
ui.button({ id: "no", label: "No" }),
],
onClose: () => app.update((s) => ({ ...s, showModal: false })),
}),
])Props
| Prop | Type | Default | Description |
|---|---|---|---|
id | string | required | Unique identifier |
title | string | - | Optional modal header title |
content | VNode | required | Main modal body |
actions | VNode[] | [] | Action row (typically buttons) |
width | number | "auto" | ~70% | Preferred modal width |
maxWidth | number | - | Maximum width constraint |
frameStyle | { background?, foreground?, border? } | - | Optional modal frame/surface colors |
backdrop | "none" | "dim" | "opaque" | { variant?, pattern?, foreground?, background? } | "dim" | Backdrop preset or extended config |
closeOnBackdrop | boolean | true | Close when clicking backdrop |
closeOnEscape | boolean | true | Close on Esc |
onClose | () => void | - | Callback when modal requests close |
initialFocus | string | - | ID to focus when modal opens |
returnFocusTo | string | - | ID to restore focus on close |
Examples
Confirmation dialog with explicit focus target
ui.modal({
id: "delete-confirm",
title: "Delete item?",
content: ui.text("This action cannot be undone."),
actions: [
ui.button({ id: "cancel", label: "Cancel" }),
ui.button({ id: "confirm", label: "Delete" }),
],
initialFocus: "cancel",
returnFocusTo: "open-delete-modal",
onClose: () => app.update((s) => ({ ...s, showDeleteModal: false })),
})Multi-action dialogs with ui.dialog
ui.dialog({
id: "save",
title: "Unsaved Changes",
message: "Save before closing?",
actions: [
{ label: "Save", intent: "primary", onPress: save },
{ label: "Don't Save", intent: "danger", onPress: discard },
{ label: "Cancel", onPress: cancel },
],
})Stacked overlays with useModalStack
const modals = useModalStack(ctx);
modals.push("login", {
title: "Login",
content: ui.text("Enter credentials"),
actions: [ui.button({ id: "login-ok", label: "Continue" })],
});
modals.push("mfa", {
title: "2FA",
content: ui.text("Enter your code"),
actions: [ui.button({ id: "mfa-ok", label: "Verify" })],
});
return ui.layers([MainContent(), ...modals.render()]);useModalStack provides push, pop, clear, current, size, and render.
Mouse Behavior
- Clicking the backdrop closes the modal when
closeOnBackdropistrue(the default). - Clicking action buttons activates them the same as pressing Enter/Space.
- Mouse events to widgets below the modal are blocked when the modal is active.
Notes
- Modals are rendered by conditionally including them in the tree (there is no
openprop). - Render modals inside
ui.layers(...)so they stack above base content. - Backdrops are rendered behind the modal.
"dim"uses a light shade pattern;"opaque"clears the area behind the modal to the theme background color. - Extended backdrop config uses object form:
variant(preset),pattern(dim glyph), and optionalforeground/backgroundcolors. width: "auto"sizes to content/actions and is clamped bymaxWidthand the viewport.useModalStackapplies focus-return wiring between stacked dialogs and keeps modal layering LIFO.
Related
- Layers - Overlay stacking container
- Layer - Generic layer primitive
- Focus Trap - Keep keyboard focus inside overlays