## Flow Modal Block & Browser API

Flow Modal is a top-level Gutenberg block that renders in the light DOM with the browser's native `<dialog>` element. Unlike the main Flow form and content-root runtimes, the modal shell does not depend on Mantine or a shadow root, so it works well with host-page triggers, static export scenarios, and ordinary Gutenberg content.

The modal supports several opening and closing routes:

- class-based triggers such as `wps-flow-modal-open--{modalId}`, `wps-flow-modal-toggle--{modalId}`, `wps-flow-modal-close`, `wps-flow-modal-cancel`, and `wps-flow-modal-ok`
- gallery-aware opener hints such as `wps-flow-gallery-target--{galleryId}` and `wps-flow-gallery-index--N` when a Flow Gallery lives inside the modal
- matching `data-wps-flow-*` attributes
- hash-based opening when the block enables `openOnHash`
- browser control through `WpSuite.plugins.flow.modals`

Inside the modal actions area, buttons can be assigned **primary**, **secondary**, or **dismiss** behavior. In Gutenberg this is managed from the selected core Button block's toolbar, and the runtime maps those roles to `defaultPrimaryAction`, `defaultSecondaryAction`, and `defaultDismissAction` when no explicit action name is present on the clicked trigger.

The browser API is exposed under `WpSuite.plugins.flow.modals` and includes:

- `open(modalId, options?)`
- `close(modalId, returnValue?)`
- `toggle(modalId, options?)`
- `closeAll(returnValue?)`
- `isOpen(modalId)`
- `get(modalId)`
- `registerAction(actionName, handler)`
- `unregisterAction(actionName)`

`registerAction()` handlers may be async. While an awaited handler is running, the modal stays open, enters a busy state, disables the standard modal action triggers, and marks the active trigger with `data-wps-flow-pending="true"` plus `aria-busy="true"`. This pending marker is deliberately generic, so it works for core Button blocks as well as fully custom button markup.

Dismiss behavior now covers every dismiss-style close path, not just the dedicated close button. That means the runtime can emit `wps-flow-modal:dismiss` for built-in close buttons, backdrop clicks, Escape handling, and other dismiss-like close flows, while `wps-flow-modal:close` continues to describe the actual close event with the final return value.

Because `wps-flow-modal:open` includes the `triggerElement`, content inside the modal can react to the opener that launched it. Flow Gallery uses that to read `wps-flow-gallery-target--{galleryId}` and `wps-flow-gallery-index--N` from a neighboring core Image or Button block so the lightbox can start on the requested 1-based image.

Practical editor details:

- the editor can seed default header, body, and actions sections
- the modal toolbar can insert or replace header, body, and actions templates
- the built-in close-button spacing in the header is only reserved when that close button is actually rendered
- the header/body/actions slot wrappers are intentionally transparent, while `.wps-flow-modal__content` provides the shared modal surface through `--wps-flow-modal-content-background`

For host-page integrations, the most useful lifecycle events are:

- `wps-flow-modal:before-open`
- `wps-flow-modal:open`
- `wps-flow-modal:before-close`
- `wps-flow-modal:close`
- `wps-flow-modal:ok`
- `wps-flow-modal:cancel`
- `wps-flow-modal:dismiss`
- `wps-flow-modal:error`

For core Button blocks, remember that custom CSS classes such as `wps-flow-modal-open--newsletter-preferences` are usually applied by WordPress to the outer `.wp-block-button` wrapper rather than the inner anchor. The runtime already accounts for that wrapper-based structure when it resolves delegated clicks.