Introduction
Vue 3's <Teleport> component renders its content at a different location in the DOM than its logical position in the component tree. When the target element does not exist at render time, Vue logs a warning and the teleported content is not rendered:
[Vue warn]: Failed to locate Teleport target with selector "#modal-container"This commonly occurs with modals, tooltips, and dropdowns that need to escape overflow:hidden containers.
Symptoms
- Teleported content does not appear on the page
- Console warning: "Failed to locate Teleport target"
- Modal/dialog components render inside the parent instead of the target container
- Works in development but fails in production builds
- Works on client-side navigation but fails on initial page load
Common Causes
- Target element does not exist in the DOM when the Teleport component renders
- Selector syntax is incorrect (missing
#for IDs,.for classes) - SSR renders the Teleport on the server but the target element is not in the server HTML
- Target element is inside another Vue component that has not yet mounted
- Dynamic target selector changes after the Teleport has already rendered
Step-by-Step Fix
- 1.Ensure the target element exists in the HTML before Vue renders:
- 2.```html
- 3.<!-- In public/index.html, add the target element -->
- 4.<body>
- 5.<div id="app"></div>
- 6.<div id="modal-container"></div>
- 7.<div id="toast-container"></div>
- 8.</body>
- 9.
` - 10.Use the correct selector syntax:
- 11.```html
- 12.<!-- Correct: # for ID, . for class -->
- 13.<Teleport to="#modal-container">
- 14.<ModalContent />
- 15.</Teleport>
<Teleport to=".toast-wrapper"> <ToastList /> </Teleport>
<!-- Also valid: query selector --> <Teleport to="body > .modal-target"> <Dialog /> </Teleport> ```
- 1.Handle SSR by disabling Teleport on the server:
- 2.```html
- 3.<Teleport to="#modal-container" :disabled="isSSR">
- 4.<ModalContent />
- 5.</Teleport>
- 6.
` - 7.```javascript
- 8.// In setup
- 9.import { ref, onMounted } from 'vue';
- 10.const isSSR = ref(true);
- 11.onMounted(() => { isSSR.value = false; });
- 12.
` - 13.Use v-if to defer Teleport until after the component is mounted:
- 14.```html
- 15.<template>
- 16.<div v-if="isMounted">
- 17.<Teleport to="#modal-container">
- 18.<Modal />
- 19.</Teleport>
- 20.</div>
- 21.</template>
<script setup> import { ref, onMounted } from 'vue'; const isMounted = ref(false); onMounted(() => { isMounted.value = true; }); </script> ```
- 1.For dynamic targets, use a ref instead of a selector:
- 2.```html
- 3.<template>
- 4.<div ref="targetRef"></div>
- 5.<Teleport :to="targetRef">
- 6.<Tooltip />
- 7.</Teleport>
- 8.</template>
<script setup> import { ref } from 'vue'; const targetRef = ref(null); </script> ```
Prevention
- Always include Teleport target elements in your base HTML template (index.html)
- Use a layout component that guarantees target elements exist before child components render
- Add Teleport target verification to your SSR hydration checklist
- Document all Teleport targets in your component library documentation
- Test Teleport components on both client-side navigation and full page load
- Use a global Teleport manager component that creates target elements dynamically if needed
- In testing, ensure the test DOM includes the target elements before mounting Teleport components