Overlays and Dialogs: Decoupling the Trigger from the Container In short: A dialog declared inside a table row renders inside that row. It inherits the row's stacking context and gets clipped by the table's . The fix is not a bigger — it is rendering the dialog somewhere else entirely. If form controls are the fiddliest small interaction, dialogs and overlays are the fiddliest large one. Users constantly need to step out of the flow. A dispatcher clicks a button and needs a confirmation before rerouting a tanker. A lab technician hovers a warning and expects a tooltip explaining a high somatic cell count. The architectural rule is simple to state and easy to violate: the trigger and the container must not be the same component. Where the dialog ends up living Here is the natural thing to write. A developer builds , and because the row already knows its own , putting the reject dialog right there is the shortest path — no output to wire up, no id to pass around. Let's be precise about what this costs, because the usual explanation of it is wrong. While the condition is false, this costs nothing. does not create an embedded view until its expression becomes truthy. A grid of 100 rows renders 100 boolean signals and zero dialog elements. There is no hidden DOM. (This is genuinely different from , which does render 100 complete dialogs and just sets on them — that version is as bad as people think.) The problem starts the moment one becomes true. When the dispatcher clicks Reject on row 47, creates the dialog's DOM inside row 47 — because that is where the template is declared. And a dialog rendered inside a table row is in the wrong place in three ways: 1. It is clipped. The grid almost certainly has or to scroll its body. An absolutely positioned backdrop inside a scroll container is clipped to that container's box. Your full-screen overlay becomes a rectangle the size of one row. 2. It cannot escape its stacking context. If any ancestor — the row, the grid, a sticky header — has a , , below 1, or , it creates a stacking context. then resolves against that ancestor instead of the viewport, and cannot lift the dialog above a sibling that sits in a different context. This is the "z-index does not work" bug, and no value fixes it. 3. It is one of a hundred possible dialogs. Every row owns its own copy of the dialog template and its own boolean. Nothing coordinates them: two rows can be open simultaneously, has no single owner, focus is not trapped, and the accessible name of "the dialog" is ambiguous. So the real cost is not wasted DOM while closed. It is that the dialog is created in a place that cannot host a dialog , plus a hundred uncoordinated copies of the same state. There is a fourth cost that shows up later: every row component now imports the dialog component, so the dialog's JavaScript is in the same chunk as the grid. If the dialog is heavy — a live route map, say — every user pays for it whether they open it or not. The fix: render it somewhere else The row should own the trigger and nothing else. The dialog gets rendered into a container at the end of , outside every stacking context and every . The Angular CDK does this. creates a global overlay container and renders your component into it: Because the overlay container is a direct child of , it is not inside anyone's and not inside anyone's stacking context. works because there is nothing to escape. The CDK also handles the parts people forget: trapping focus inside the dialog, restoring focus to the trigger on close, setting and the right role, marking the rest of the page inert, and closing on and backdrop click. Lazy-loading a heavy dialog Because the trigger no longer imports the dialog component, you can load it on demand: The mapping library is now in its own chunk, downloaded by the users who open the map. An overlay facade You do not want scattered across twenty repositories with twenty different sets of options. Wrap it once, in the library: Feature code becomes readable: One confirmation design across the whole platform, and feature teams cannot accidentally create a dialog that closes on backdrop click when it is about to send a tanker to the wrong place. The overlay hierarchy Not everything is a modal. Three levels, three tools: Kind Behaviour Tool --- --- --- Dialog — confirmation, data entry Centre screen, blocks the page, traps focus Side panel — detail views, history Slides from the edge, may not block + Popover / tooltip / dropdown Tethered to a trigger, follows on scroll with a position strategy The third is a genuinely different mechanism: it needs to track the trigger element's coordinates and reposition when the user scrolls or the element moves. That is what 's is for. And for the patterns covers — Menu, Menubar, Combobox, Autocomplete — use those instead. They handle the overlay and the keyboard model, which is the part that takes longest to get right. "But I thought a native element solved all of this now" This is a good i…