$ rezi
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

PropTypeDefaultDescription
idstringrequiredUnique identifier
titlestring-Optional modal header title
contentVNoderequiredMain modal body
actionsVNode[][]Action row (typically buttons)
widthnumber | "auto"~70%Preferred modal width
maxWidthnumber-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
closeOnBackdropbooleantrueClose when clicking backdrop
closeOnEscapebooleantrueClose on Esc
onClose() => void-Callback when modal requests close
initialFocusstring-ID to focus when modal opens
returnFocusTostring-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 closeOnBackdrop is true (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 open prop).
  • 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 optional foreground/background colors.
  • width: "auto" sizes to content/actions and is clamped by maxWidth and the viewport.
  • useModalStack applies focus-return wiring between stacked dialogs and keeps modal layering LIFO.
  • Layers - Overlay stacking container
  • Layer - Generic layer primitive
  • Focus Trap - Keep keyboard focus inside overlays

On this page