--- title: Lifecycle and route interception description: Understand results, queues, Host lifetime, and router adapters. order: 20 toc: - id: result-contracts title: Result contracts - id: queue title: Queues and Hosts - id: dismissal title: Dismissal - id: navigation title: Route interception --- ## Result contracts {#result-contracts} `openConfirm` uses `false` for cancellation, `openPrompt` uses `null`, and custom Modals, Sheets, and Drawers use `undefined` when no result is supplied. `close(value)` is checked against the generic passed to `openModal()`. ```tsx import { openModal } from "@workspace/overlays/modal" const productId = await openModal({ title: "Choose a product", content: ({ close }) => ( close(product.id)} /> ), }) ``` ## Queues and Hosts {#queue} Within one Scope, each feature has a lazily created channel and Base UI handle. Calls of the same kind enter a FIFO queue and the next request appears after the previous closing animation completes. Different features can remain visible together, so a Modal may open a Confirm. Unmounting a Host settles its pending requests with their cancellation values. React Strict Mode's simulated unmount does not incorrectly cancel requests. ## Dismissal {#dismissal} Custom content can call `dismiss()`. The close button, Escape key, and backdrop press enter the same cancellation path. Setting `dismissible: false` blocks external dismissal, while the content may still explicitly call `close(result)` or `dismiss()`. ## Route interception {#navigation} `@workspace/overlays/navigation` depends only on Scope, not on a concrete overlay feature or router: ```tsx import { OverlayNavigationGuard } from "@workspace/overlays/navigation" const adapter: OverlayNavigationAdapter = { subscribe(listener) { return router.block((transition) => { listener({ cancel: transition.reset, proceed: transition.retry, }) }) }, } ``` The default Guard examines the current and named local Scopes and finds the last-opened layer. It dismisses an allowed layer and cancels navigation. A layer with `dismissible: false` remains open and keeps blocking. It calls `proceed()` only when no layer is open. The adapter's `proceed()` must continue the exact blocked attempt instead of starting a new navigation that triggers the blocker again. Pass a custom `interceptor` to replace the policy.