Overview
Blueprint’s Dialog component is a portal-rendered modal with a built-in focus trap, Esc dismissal, backdrop click handling, and ARIA roles. It targets the same use cases as browser-native <dialog>: confirmations, detail views, multi-step flows, and form-in-modal patterns. The key decisions are where to mount the portal, how to manage open state, and how to keep focus behavior correct when dialogs are nested or paired with a form library.
Wrap the app in OverlaysProvider for predictable portal mounting
By default, Blueprint overlays (Dialog, Popover, Tooltip, Toaster) mount onto document.body. That works for most apps. When a parent applies transform, filter, or contain: layout, it creates a stacking context that clips portaled children or breaks position: fixed.
OverlaysProvider lets you declare the portal target explicitly and avoids this class of bug proactively.
import { OverlaysProvider } from "@blueprintjs/core";
function App() {
return (
<OverlaysProvider>
<Router />
</OverlaysProvider>
);
}Mount OverlaysProvider once near the root, outside any element that applies CSS containment or a transform. See blueprint-overlays for the z-index layering rules that follow from this.
Control open state in the parent; never inside the Dialog
Dialog is a controlled component. Manage isOpen with useState in the component that owns the trigger. Blueprint does not have an uncontrolled dialog that manages its own open state.
function RecordPanel({ record }: { record: Record }) {
const [isOpen, setIsOpen] = useState(false);
return (
<>
<Button onClick={() => setIsOpen(true)}>Edit</Button>
<Dialog
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Edit record"
>
<DialogBody>
<RecordForm record={record} onSave={() => setIsOpen(false)} />
</DialogBody>
</Dialog>
</>
);
}Pass onClose to handle Esc and backdrop clicks. If either is omitted, those dismissal paths stop working. When using a MultistepDialog, pass onClose at the top level; individual steps do not need separate close handlers.
Always provide a title prop; never omit it
The title prop renders a visible heading and sets aria-labelledby on the dialog element. Screen readers announce the title when the dialog opens. Without a title, the dialog has no accessible name and screen reader users have no context for what opened.
<Dialog
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Confirm account deletion"
icon="warning-sign"
>
<DialogBody>
<p>This action is permanent and cannot be undone.</p>
</DialogBody>
<DialogFooter
actions={
<>
<Button onClick={() => setIsOpen(false)}>Cancel</Button>
<Button intent={Intent.DANGER} onClick={handleDelete}>
Delete account
</Button>
</>
}
/>
</Dialog>Use DialogBody for the scrollable content area and DialogFooter for action buttons. Both sub-components apply the correct padding and the DialogFooter right-aligns actions by default. Do not nest raw <div> elements in place of these sub-components; the layout will break in multi-step dialogs.
Use MultistepDialog for wizard flows
A multi-step dialog keeps the user in a single modal through a sequence of steps. Blueprint’s MultistepDialog manages navigation, the back/next buttons, and step state.
<MultistepDialog
isOpen={isOpen}
onClose={() => setIsOpen(false)}
title="Set up workspace"
finalButtonProps={{ text: "Finish", onClick: handleFinish }}
>
<DialogStep id="profile" title="Profile" panel={<ProfileStep />} />
<DialogStep id="team" title="Team" panel={<TeamStep />} />
<DialogStep id="plan" title="Plan" panel={<PlanStep />} />
</MultistepDialog>Each DialogStep renders its panel only when active. Do not conditionally render your own step panels inside a plain Dialog; MultistepDialog handles keyboard navigation and the step indicator. See forms for form validation inside each step.
Avoid mixing Blueprint Dialog with a Radix-based overlay in the same surface
Radix and Blueprint each maintain a focus-trap and an overlay stack. Nesting one inside the other produces two competing focus traps. The inner dialog may trap focus correctly, but the outer one may not restore focus when the inner closes.
If your app uses shadcn components elsewhere, keep the overlay systems separate by surface. Use Blueprint dialogs for the Blueprint-rendered shell (dashboard, data tables) and shadcn dialogs for the shadcn-rendered shell (marketing page, settings form). Never render a Blueprint Dialog as a child of a Radix Dialog.Content or vice versa. See blueprint-overlays for the full overlay layering guidance.
Test keyboard navigation after every dialog change
Blueprint’s Dialog implements the ARIA dialog pattern. The focus trap ensures that Tab and Shift+Tab cycle through focusable elements inside the dialog. Esc closes it. The trigger button regains focus on close.
Verify these behaviors after any structural change to the dialog content:
- Tab cycles through all interactive elements in the dialog body and footer.
Esccloses the dialog and returns focus to the trigger.- Clicking the backdrop closes the dialog.
- Screen reader announces the title on open.
A missing tabIndex, a pointer-events: none style on a button, or a display: none on content can break the focus cycle without a visual sign. See accessibility for the full keyboard test checklist.