When to use
Use this skill when:
- •Adding confirmation dialogs, alerts, or prompts
- •Building layered/overlay UI
- •Need focus trapping within a dialog
Source of truth
- •
packages/core/src/widgets/useModalStack.ts—useModalStack()hook - •
packages/core/src/widgets/types.ts—ModalProps,LayersProps - •
packages/core/src/widgets/ui.ts—ui.modal(),ui.layers() - •
packages/core/src/ui/recipes.ts—recipe.modal()andrecipe.surface()for design-system-consistent modal styling
Steps
Option A: useModalStack (recommended for multiple modals)
- •Use
useModalStack()inside adefineWidget:typescriptconst modals = useModalStack(ctx); // Push a modal modals.push("confirm", { title: "Confirm", content: body, onClose: () => modals.pop(), }); // Include in view return ui.layers([mainContent, ...modals.render()]);
Option B: ui.modal (simple single modal)
- •
Use
ui.modal()directly with a state-controlled flag:typescriptreturn ui.layers([ mainContent, ...(state.showModal ? [ ui.modal({ id: "confirm-modal", title: "Confirm", initialFocus: "confirm-ok", returnFocusTo: "open-confirm", onClose: () => app.update((s) => ({ ...s, showModal: false })), content: ui.text("Are you sure?"), actions: [ui.button({ id: "confirm-ok", label: "OK" })], }), ] : []), ]); - •
Overlay ordering note:
- •Modal, dropdown, and toast overlays share unified z-ordering through the LayerRegistry.
- •Prefer
ui.layers([...])as the common overlay composition root.
Verification
- •Modal opens and closes correctly
- •Focus traps within the modal (Tab doesn't escape)
- •Focus returns to previous element on close