Fix: Invalid Teleport Target in Vue.js

Error message:
Invalid Teleport target: {targetSelector}
Components 2025-01-25

What Causes This Warning?

This warning occurs when <Teleport> cannot find the target DOM element specified in its to prop. The element either doesn’t exist yet, has a typo in the selector, or is being rendered conditionally.

The Problem

<!-- ❌ Target element doesn't exist -->
<template>
  <Teleport to="#modal-container">
    <div class="modal">Modal content</div>
  </Teleport>
</template>

<!-- No element with id="modal-container" in the DOM! -->

The Fix

Ensure Target Element Exists

<!-- index.html -->
<!DOCTYPE html>
<html>
<body>
  <div id="app"></div>

  <!-- ✅ Add teleport target -->
  <div id="modal-container"></div>
</body>
</html>
<!-- Component.vue -->
<template>
  <Teleport to="#modal-container">
    <div class="modal">Modal content</div>
  </Teleport>
</template>

Use Disabled Prop When Target Not Ready

<script setup>
import { ref, onMounted } from 'vue'

const targetReady = ref(false)

onMounted(() => {
  // Check if target exists
  targetReady.value = !!document.querySelector('#modal-container')
})
</script>

<template>
  <!-- ✅ Disable teleport until target is ready -->
  <Teleport to="#modal-container" :disabled="!targetReady">
    <div class="modal">Modal content</div>
  </Teleport>
</template>

Create Target Dynamically

<script setup>
import { onMounted, onUnmounted } from 'vue'

let container: HTMLElement | null = null

onMounted(() => {
  // ✅ Create target element dynamically
  container = document.createElement('div')
  container.id = 'dynamic-modal-container'
  document.body.appendChild(container)
})

onUnmounted(() => {
  container?.remove()
})
</script>

<template>
  <Teleport to="#dynamic-modal-container">
    <div class="modal">Modal content</div>
  </Teleport>
</template>

Common Scenarios

SSR/Nuxt Applications

<script setup>
import { ref, onMounted } from 'vue'

// ❌ Target doesn't exist during SSR
const isMounted = ref(false)

onMounted(() => {
  isMounted.value = true
})
</script>

<template>
  <!-- ✅ Only teleport on client -->
  <Teleport v-if="isMounted" to="body">
    <div class="modal">Modal content</div>
  </Teleport>
</template>

Nuxt with ClientOnly

<template>
  <!-- ✅ Use ClientOnly in Nuxt -->
  <ClientOnly>
    <Teleport to="#modal-container">
      <div class="modal">Modal content</div>
    </Teleport>
  </ClientOnly>
</template>

Multiple Teleport Targets

<!-- index.html -->
<body>
  <div id="app"></div>
  <div id="modals"></div>
  <div id="tooltips"></div>
  <div id="notifications"></div>
</body>
<template>
  <Teleport to="#modals">
    <Modal v-if="showModal" />
  </Teleport>

  <Teleport to="#tooltips">
    <Tooltip v-if="showTooltip" />
  </Teleport>

  <Teleport to="#notifications">
    <Toast v-for="toast in toasts" :key="toast.id" />
  </Teleport>
</template>

Teleport to Body

<!-- ✅ body always exists -->
<template>
  <Teleport to="body">
    <div class="fullscreen-modal">
      Content goes here
    </div>
  </Teleport>
</template>

Conditional Teleport

<script setup>
const props = defineProps<{
  appendToBody?: boolean
}>()
</script>

<template>
  <!-- ✅ Conditionally teleport -->
  <Teleport to="body" :disabled="!appendToBody">
    <div class="dropdown-menu">
      Menu content
    </div>
  </Teleport>
</template>

Dynamic Target

<script setup>
import { computed } from 'vue'

const props = defineProps<{
  targetId: string
}>()

const teleportTarget = computed(() => `#${props.targetId}`)
</script>

<template>
  <Teleport :to="teleportTarget">
    <div>Teleported content</div>
  </Teleport>
</template>

Valid Target Selectors

<!-- CSS selector -->
<Teleport to="#my-id">

<!-- Class selector -->
<Teleport to=".my-class">

<!-- Data attribute -->
<Teleport to="[data-modal-container]">

<!-- Element tag (first match) -->
<Teleport to="body">

<!-- DOM element reference -->
<Teleport :to="elementRef">

Quick Checklist

  • Verify target element exists in the DOM
  • Check for typos in the selector
  • Add target element to index.html
  • Use :disabled when target might not exist
  • For SSR, ensure client-side only rendering
  • Use body as a fallback target that always exists