Modals nested deep in a component tree have a habit of getting clipped by an ancestor's overflow: hidden or losing z-index battles with a sibling that nobody thought to check. Vue 3's Teleport component lets you render a component's output somewhere else in the DOM entirely, typically straight to a dedicated node at the end of body, which sidesteps the problem outright rather than patching around it with ever-more-specific CSS.
Migrating our modal and toast components to Teleport removed a whole category of workarounds we'd accumulated over several years: fixed-position hacks, manually bumped z-index values, and the occasional component that had to be moved higher in the tree purely so its overflow wouldn't clip a child that had nothing to do with it logically. New modals no longer need a specificity war to display correctly, and reviewing a pull request that adds one no longer means checking three ancestor components for overflow rules.
The migration was mechanical for the most part. Each modal or toast component's template got wrapped in a `<Teleport to="#modal-root">` tag, and the component's internal state and event handling didn't need to change at all, since Teleport only moves where the DOM output lands, not how the component behaves logically or where it lives in the Vue component tree. Focus management needed a second pass, since a teleported modal is no longer adjacent in the DOM to the element that opened it, and screen reader and keyboard-navigation behavior depends on that relationship more than most teams realize until they test it directly.
Nested modals turned out to be the trickiest case to get right. A confirmation dialog opened from within an already-teleported modal needs to land visually above its parent, but both are teleporting to the same root node, so DOM order rather than any explicit z-index stacking now determines which one renders on top. We ended up building a small internal composable that manages a stack of currently-open teleported overlays and assigns each one an incrementing z-index on open, which sounds like exactly the kind of manual z-index bookkeeping Teleport was supposed to let us stop doing, but only applies to the rare case of genuinely stacked overlays rather than the whole component library.
Testing also needed a second look once components started rendering outside their logical position in the tree. Our existing component tests used Vue Test Utils' mounting helpers, which by default only search within the mounted component's own DOM subtree, so assertions that used to find a modal's content directly under its parent silently stopped finding anything once that content started rendering under the teleport target instead. Updating our test helpers to search the full document, rather than just the mounted subtree, fixed it, but it was a quiet failure mode that took a confused afternoon to properly diagnose the first time it came up.
We'd recommend any team making this move budget specific time for the accessibility pass rather than treating it as a footnote to the migration. Toast notifications in particular needed an `aria-live` region added at the teleport target, since screen readers won't reliably announce content that just appears somewhere new in the DOM without an explicit live-region hint telling them to pay attention to it.
One thing worth flagging for anyone doing the same migration: Teleport targets have to exist in the DOM before the component mounts, so a modal root div rendered conditionally elsewhere in the app can cause a teleport to silently fail on first render. We now keep a single static modal-root node in the base layout that's always present, which avoids the issue entirely and has been a reliable pattern across every project we've applied it to since.