Modals and portals

Anchoring inside dialogs.

Why modals are hard

A step can point at a control inside a dialog, a popover, or a command palette. All of them render in their own portal, and each brings a problem:

  • Stacking. A dialog at a high z-index paints over a scrim beneath it.
  • inert and aria-hidden. Dialog libraries mark everything outside the dialog as inert, which would make the tour’s own buttons unclickable.
  • Focus traps. A trap inside the dialog cannot reach a sibling portal, so Tab never lands on Next.

This is where most tour libraries break, and it is not a small edge case.

What Cairn does

When the target lives inside a dialog, the overlay portals into that dialog rather than onto document.body. It then inherits the dialog’s stacking context, its interactivity, and its focus scope — all three problems solved by placement.

Nothing to configure

Point a step at an anchor inside your dialog. Detection is automatic — it looks for [role="dialog"], [role="alertdialog"] and dialog[open], which covers Radix, Headless UI, MUI and native dialogs.

{ anchor: anchors.settings.difficulty, title: "Inside a modal" }

Opening the modal mid-tour

Use a click step on the button that opens it. The user opens the dialog themselves, and the next step’s anchor is waited for until it mounts.

steps: [
  {
    anchor: anchors.page.settingsButton,
    title: "Open settings",
    body: "The next step is inside the dialog.",
    advanceOn: { type: "click" },
  },
  { anchor: anchors.settings.difficulty, title: "Inside a modal" },
]