Skip to content
Layers
Esc
navigateopen⌘Jpreview
On this page

Dismissal blockers

Gate user-intent dismissal with addBlocker, dismissing, dismissAll modes, and force.

Blockers let you ask “discard unsaved changes?” before a layer closes. They gate engine dismissal paths — call.end/call.dismiss and stack.dismiss/stack.dismissAll. Backdrop clicks and Esc are not core events; wire them to call.end/call.dismiss if they should consult blockers. System teardown is never gated.

See Blockers for the full model.

Register blockers

Two scopes, one gate — any veto blocks the attempt:

// instance scope — dirty state on this layer
const dispose = call.addBlocker(() => { 
  if (isDirty) return false; // veto
  return true; // allow
});

// stack scope — app-wide policy
const disposeStack = stack.addBlocker((layer) => {
  return !layer.payload.locked;
});

Both return a disposer. Predicates may be async; dismiss awaits them.

Return value and force

call.end and call.dismiss return Promise<boolean>true if dismissed, false if vetoed:

const didDismiss = await call.dismiss(); // void-R: omit the response
if (!didDismiss) {
  // show confirm UI — your own layer, not opened by core
}

Bypass blockers with { force: true }:

await call.end(response, { force: true });
await call.dismiss(response, { force: true });
// void-R: await call.end(undefined, { force: true });

While blockers evaluate, dismissing is true on the layer state — disable close buttons during async confirm:

function Editor({ call, dismissing }: LayerComponentProps<void, void>) {
  return (
    <button disabled={dismissing} onClick={() => void call.dismiss()}>
      Close
    </button>
  );
}

dismissAll modes

stack.dismissAll(...args) is async (omit response for void-R):

Mode Behavior
"skipBlocked" (default) Attempt each; close permitted, leave blocked open
"stopAtBlocked" Process in order; halt at first blocked
"force" Bypass all blockers
await stack.dismissAll(undefined, { mode: "stopAtBlocked" });
// void-R + default mode: await stack.dismissAll();

Default mode is configurable via StackOptions.dismissAllMode or LayerClientOptions.defaultStackOptions.

What blockers skip

Path Blockers
call.end / call.dismiss Honored
stack.dismiss / stack.dismissAll Honored
cancelQueued (serial, never mounted) Skipped
cancelAll / component unmount / host disconnect Skipped — rejects open() with LayerCancelledError
Layer-group cascade on parent dismiss Skipped — child cancelAll({ reason: "parentDismiss" }); guard the parent

Last updated on August 8, 2026

Was this page helpful?