Skip to content
← All questions
⚡ Intermediate

What are Teleport, Fragments, and Suspense?

🧩 Components⏳ Suspense🌀 Teleport

These are three built-in features introduced in Vue 3. Each solves a different problem: rendering content outside the component tree, allowing multiple root elements, and handling async dependencies with loading states.

Teleport

<Teleport> renders its children into a different part of the DOM, outside the parent component's element. The component logic (props, events, reactivity) stays in place; only the DOM output moves.

vue
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
</script>

<template>
  <button @click="showModal = true">Open</button>

  <Teleport to="body">
    <div v-if="showModal" class="modal-overlay">
      <div class="modal">
        <p>This renders as a direct child of body</p>
        <button @click="showModal = false">Close</button>
      </div>
    </div>
  </Teleport>
</template>
Open in Vue Playground

Without Teleport, a modal inside a deeply nested component inherits all parent CSS (overflow: hidden, z-index, transform), which can clip or misposition it. Teleporting to <body> sidesteps those issues. The to prop accepts any CSS selector or DOM element.

Common uses: modals, tooltips, dropdown menus, notifications. Anything that needs to visually escape its parent's layout.

Fragments

In Vue 2, every component needed a single root element. This forced unnecessary wrapper <div>s:

vue
<!-- Vue 2: required single root -->
<template>
  <div>
    <header>Header</header>
    <main>Content</main>
  </div>
</template>
Open in Vue Playground

Vue 3 supports fragments, meaning multiple root elements with no wrapper:

vue
<!-- Vue 3: multiple roots, no wrapper needed -->
<template>
  <header>Header</header>
  <main>Content</main>
  <footer>Footer</footer>
</template>
Open in Vue Playground

One caveat: fallthrough attributes don't work automatically with multi-root components because Vue doesn't know which root to apply them to. You need to bind $attrs explicitly.

Suspense

<Suspense> shows fallback content while waiting for async child components to resolve. It works with components that have an async setup() or that are loaded with defineAsyncComponent.

vue
<template>
  <Suspense>
    <template #default>
      <UserDashboard />
    </template>
    <template #fallback>
      <LoadingSpinner />
    </template>
  </Suspense>
</template>
Open in Vue Playground

If UserDashboard has an async setup (returns a promise), Suspense shows LoadingSpinner until the promise resolves. You can also handle errors with onErrorCaptured in the parent.

Note: Suspense is still an experimental API. The core behavior is stable, but the API may have minor changes.

See also: How does Suspense work for async components? · What are async components? · What are fallthrough attributes?

References

Released under the MIT License.