Skip to main content

How to read this page

Every rule on this page is labeled with a tier badge (what kind of rule it is) and a platform badge (where it applies).

Tiers

Must Breaks if ignored. Unsupported or code-enforced. Recommendation Bad idea but works. No code guard, but you will regret it. Platform behavior Factual platform difference. Not a ban. Something to design around. Context-dependent Depends on platform or scenario. Read carefully.

Platforms

Web Applies to web only. Mobile Applies to mobile (iOS, Android) only. Both Applies to both platforms.

Guiding principles

These are the foundational principles that everything else on this page derives from. Keep them in mind as you design.
  1. Web is a single-page application. It renders whatever is pushed to the DOM.
  2. Mobile maintains a navigation history. Users can navigate back.
  3. The main process and its subprocesses share the same navigation. Call Activities should therefore only be used for UI that is part of the linear root navigation.
  4. Subprocesses started from User Task actions must be only optional and secondary flows, meaning modals.
  5. Be very intentional about when user tasks stay in the main process vs. move to Call Activities. Keep them in the main process when you want the user to be able to go back. Keep them in Call Activities when you don’t want the user to be able to go back once the subprocess has finished.
  6. Modals must be displayed on top of existing content. The main process must not start with a modal.
  7. Asynchronously started subprocesses cannot have user tasks. Multiple sibling subprocesses cannot share the same navigation history at the same time. The current subprocess must finish before a new one begins.
  8. Subprocesses without UI must not contain subprocesses with UI.
  9. On mobile, you can have navigation inside the same modal. Use this when the screens refer to the same user task split across multiple screens. Avoid nesting modals.
  10. On web, only one modal can be displayed at a time. Nesting modals replaces the previous modal, and you cannot return to it.
  11. Keep hierarchies flat. Prefer the flattest navigation hierarchy that solves the problem. The Designer is stricter on mobile and more lenient on web, which can produce web-only configurations that do not translate. When in doubt, design against the stricter (mobile) constraints.

Modals must overlay existing content

Must Both Why. A modal is rendered on top of existing content. If the process starts with a modal, there is nothing underneath. The modal becomes the only visible surface, which breaks its interruption semantics. It is also not in the back stack (see Navigation back-stack behavior on mobile), so dismissing it from a first-screen position has no defined return target. The user experience cannot begin with a modal. Do this instead. Make the first screen a Page. Open the modal over that Page when an interruption is actually needed (confirmation, acknowledgment, short focused task).

Chaining modals: depends on platform and on whether subprocesses are involved

Context-dependent Both The “don’t chain modals” rule is not absolute. It depends on where you are and what’s in the chain. Why.
  • Web: opening a second modal replaces the first modal in the DOM. You cannot return to the first modal. If that loss of back-navigation is acceptable for your scenario, it’s a valid design. If not, don’t chain.
  • Mobile: two “modals” can be the same modal with internal navigation between them. This is allowed when the screens represent steps of the same user task. Do not nest separate modals on top of each other.
  • Hard breakage in both platforms: chains that mix subprocesses become unpredictable. Patterns like [Page] → [Modal] → [Modal] → [Page] or [Page] → [Modal] → [Page] → [Modal] break when subprocesses sit inside the chain, because subprocess lifecycle and shared navigation history collide.
Do this instead.
  • Same-task multi-step on mobile: use internal modal navigation, not a second modal.
  • Need return-to-previous-modal on web: convert to Pages or Stepper steps.
  • Sequential independent tasks: use a Stepper, not chained modals.
  • Never mix subprocesses into a modal chain.

Do not use modals as loaders

Recommendation Both Why. A modal is an interruption that requires the user to act. A loading state isn’t. The user can’t interact with “wait.” Using a modal as a loader blocks the user unnecessarily and misuses the modal’s interruption semantics. Do this instead. Use component-level loaders: skeleton screens, spinner overlays, or the Show loader option on UI actions. Loaders belong on the components that are loading, not on top of the whole screen. Custom loader APIs in the SDKs. Each renderer SDK exposes a custom loader API so the container app can replace the default loader UI per scenario (start process, save data, resume, etc.). Use those instead of a modal:

No modal over modal

Context-dependent Both Why.
  • Web: only one modal can be displayed at a time. Nesting modals replaces the previous modal, and you cannot return to it (Guiding principle 10).
  • Mobile: the OS automatically pops the current modal when the flow advances to the next screen (see Navigation back-stack behavior on mobile). This is why two modals cannot be stacked on mobile at all. The recommended way to have navigation between modal-rendered screens is a single Modal navigation area with exactly two User Tasks inside, which mobile renders as two linked screens within the same modal. The SDK does not enforce this as a cardinality rule, so configurations with more UTs inside a Modal area may behave unpredictably.
If you do nest modals, the choice must be intentional and the consequences understood. On web, the previous modal is gone for good. On mobile, the OS auto-pops the first as soon as the second is pushed. Don’t reach for nested modals as a shortcut. Do this instead. On mobile, use internal modal navigation for same-task multi-step flows (one Modal nav area, two UTs). On web, use Pages or Stepper steps when you need to return to a previous screen.

No two modals simultaneously

Must Both Why. Only one modal can be displayed at a time (Guiding principle 10). The renderer does not reliably handle concurrent modals. Do this instead. Resolve the current modal before opening another.

Modals are not navigation containers

Must Both Why. A modal must overlay existing content (Guiding principle 6). It is not a container for pages, steppers, tabs, or other navigation structures. Nesting navigation areas inside a modal breaks the Designer’s render and dismiss logic. Do this instead. Keep the modal as a leaf in the navigation tree. Put pages, steppers, and tab bars at the root of their respective zones, not inside a modal. See Navigation best practices: What “child navigation areas” means for correct vs incorrect nav trees.

Max modal depth is 1

Must Both Why. Direct consequence of Guiding principles 9 and 10, and of how the OS back stack treats modals on mobile (see Navigation back-stack behavior on mobile). On web, a second modal replaces the first. On mobile, the OS auto-pops the first modal the moment the second would be pushed, so two modals never coexist. The only scenario that produces a nominal depth greater than 1 is nested Call Activities, where each activity contains its own modal; this is a hierarchy artifact, not modal stacking, and is not a reason to plan around depth greater than 1. Enforcement. This rule is enforced by design, not by a runtime guard. The Designer will accept configurations with deeper modal nesting, and the template config will load, but the rendering behavior in those cases is unsupported. Don’t rely on the platform to catch a depth violation. Do this instead. Design to a single modal depth. If you need a multi-step flow on mobile inside a modal, use internal modal navigation. If you need multiple distinct screens, use a Stepper or Pages.

No cyclic modal paths

Recommendation Both Why. There is no code-level cycle detection for modal paths. A process loop that returns to a modal already in the current navigation path can confuse the user’s navigation history and mental model. Do this instead. Resolve the modal before looping. If the modal is part of the main sequential flow, replace it with a Step.

Modals are dismissed only by user interaction

Must Both Why. Modal lifecycle should be user-driven. A modal is an interruption; ending the interruption is the user’s signal that they are ready to continue. Backend-driven auto-dismiss (timers, SSE events that the user didn’t initiate, flow progression the user isn’t aware of) breaks that contract. User-initiated chains are fine. A dismiss that follows a user action is compliant, even when the close is delayed. Example: in UI Flows, a user clicks a button configured with Start Workflow and Dismiss on completion. The modal closes once the workflow finishes, but the dismiss is still a direct consequence of the user’s click. That is not auto-dismiss. What to avoid. Timer-based dismiss. Backend events that arrive independent of user action. Automatic close on flow progression without user acknowledgment. Do this instead. Close modals via user triggers: confirm button, cancel button, close icon, backdrop click, or a user-initiated action whose completion dismisses the modal.

Don’t chain a modal user task to a Call Activity

Must Both Why. Invoking a Call Activity from inside a modal user task dismisses the modal. There is no restoration path: the modal is not brought back when the Call Activity finishes, and the user loses the interruption context. Do this instead. Resolve the modal first, then advance to the Call Activity from the Page or Step underneath. Don’t put the Call Activity trigger inside the modal itself.

Subprocess design

A child subprocess does not dismiss its parent

Must Both Why. The engine guarantees parent isolation. When a Call Activity or Embedded subprocess finishes, it signals the parent to continue, but does not terminate the parent. Dismissal does not cascade upward. Do this instead. Treat a child’s completion as a signal that resolves only the child. To propagate results up to the parent, use Append Params to Parent Process (to write into the parent’s data model) or Send data to user interface with target Parent process (to push data into the parent’s UI in real time).

Call Activity subprocesses with Pages or Steppers appear in mobile back-nav

Platform behavior Mobile Why. Mobile maintains a navigation history. When a Call Activity subprocess renders a Page or Stepper, those screens land in the shared back stack and the user can tap back into them while the subprocess is running. Modals, by contrast, are auto-popped on forward navigation and never appear in back-nav. What happens when the subprocess finishes. The user is returned to the last screen of the parent process, or in some scenarios to the first screen of the Call Activity. The subprocess’s screens are cleared from the back stack, so the user cannot return to them. See Navigation back-stack behavior on mobile for the full mental model. Design for it. Back-navigation targets should land on meaningful states, not orphaned or mid-step screens. Ask: “if the user taps back from the next screen, what makes sense to show them?” Context rule for modals after a Call Activity. If a modal appears immediately after a Call Activity finishes, its context belongs to that Call Activity, not to the parent process. The user has not seen the parent’s last screen for many screens; a modal referencing that parent context will feel disconnected. Either keep the modal inside the Call Activity, or make sure its content references what the user just completed.

Avoid nesting a UI subprocess under a headless one

Recommendation Both Why. Subprocesses without UI should not contain subprocesses with UI (Guiding principle 8). When the immediate parent has no screens, the UI subprocess has no natural parent context, and the hierarchy becomes harder to read, maintain, and debug. Do this instead. If a subprocess needs UI, its immediate parent should also have UI. Restructure so the UI subprocess is a sibling of the headless one, or promote the parent to have UI.

UI subprocesses must start synchronously

Must Both Why. Asynchronously started subprocesses cannot have user tasks (Guiding principle 7). An async UI subprocess races with the parent’s token advance, and the UI gets dismissed before the user can complete it. Do this instead. Use sync mode for any subprocess that contains user tasks. Reserve async mode for headless subprocesses (service tasks, integrations, notifications).

Starting a subprocess from a User Task action

Must Both Why. Subprocesses started from User Task actions must be only optional and secondary flows, meaning modals (Guiding principle 4). Mandatory UT actions that launch subprocesses block the flow if the subprocess cannot start. Do this instead. Use modal-only navigation for the entry point, and keep the starting action optional. If the journey is mandatory, move it into a Call Activity on the main process, not behind a UT action.

Navigation best practices

Practical guide for choosing between Pages, Modals, Steppers, Tab Bars, and Zones.

Subprocess management

Configuring, starting, and executing subprocesses, including mobile navigation behavior.

Navigation areas

Reference for all navigation area types and platform-specific capabilities.
Last modified on April 24, 2026