Files
agent-skills/skills/vue-best-practices/reference/teleport-logical-hierarchy-preserved.md
Jason Woltje f5792c40be feat: Complete fleet — 94 skills across 10+ domains
Pulled ALL skills from 15 source repositories:
- anthropics/skills: 16 (docs, design, MCP, testing)
- obra/superpowers: 14 (TDD, debugging, agents, planning)
- coreyhaines31/marketingskills: 25 (marketing, CRO, SEO, growth)
- better-auth/skills: 5 (auth patterns)
- vercel-labs/agent-skills: 5 (React, design, Vercel)
- antfu/skills: 16 (Vue, Vite, Vitest, pnpm, Turborepo)
- Plus 13 individual skills from various repos

Mosaic Stack is not limited to coding — the Orchestrator and
subagents serve coding, business, design, marketing, writing,
logistics, analysis, and more.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:27:42 -06:00

169 lines
4.2 KiB
Markdown

---
title: Teleport Preserves Logical Component Hierarchy
impact: LOW
impactDescription: Understanding that Teleport only changes DOM structure, not Vue's component tree
type: best-practice
tags: [vue3, teleport, component-hierarchy, props, events]
---
# Teleport Preserves Logical Component Hierarchy
**Impact: LOW** - A common misconception is that Teleport breaks the Vue component relationship. In reality, `<Teleport>` only alters the rendered DOM structure, not the logical hierarchy. Props, events, provide/inject, and Vue Devtools all work as if the component wasn't teleported.
## Task Checklist
- [ ] Continue using props and events normally with teleported components
- [ ] Use provide/inject across teleported boundaries
- [ ] Check Vue Devtools for component location (shows logical parent, not DOM location)
**Key Concept:**
```vue
<!-- ParentComponent.vue -->
<template>
<div class="parent">
<Teleport to="body">
<!-- ChildComponent is logically still a child of ParentComponent -->
<!-- even though it renders in <body> -->
<ChildComponent
:message="parentMessage"
@update="handleUpdate"
/>
</Teleport>
</div>
</template>
<script setup>
import { provide, ref } from 'vue'
const parentMessage = ref('Hello from parent')
// Provide still works across teleport
provide('theme', 'dark')
function handleUpdate(value) {
// Events bubble up through logical hierarchy, not DOM
console.log('Received from teleported child:', value)
}
</script>
```
```vue
<!-- ChildComponent.vue -->
<template>
<div class="modal">
<p>{{ message }}</p>
<p>Theme: {{ theme }}</p>
<button @click="$emit('update', 'new value')">Update</button>
</div>
</template>
<script setup>
import { inject } from 'vue'
// Props work normally
defineProps(['message'])
// Inject works across teleport boundary
const theme = inject('theme')
// Emit works normally
defineEmits(['update'])
</script>
```
## What Teleport Changes vs. Preserves
| Aspect | Changed? | Notes |
|--------|----------|-------|
| DOM position | Yes | Content moves to `to` target |
| CSS inheritance | Yes | Styles inherit from new DOM parent |
| Props | No | Work exactly as without Teleport |
| Events (emit) | No | Bubble through logical hierarchy |
| Provide/Inject | No | Work across teleport boundaries |
| Vue Devtools | No | Shows logical component tree |
| Slots | No | Work normally |
| Template refs | No | Parent can ref teleported content |
## Practical Example: Modal with Form
```vue
<!-- ModalForm.vue -->
<template>
<Teleport to="body">
<div v-if="visible" class="modal-overlay">
<form @submit.prevent="handleSubmit" class="modal-form">
<!-- Slot content receives parent's scope -->
<slot :formData="formData" />
<button type="submit">Submit</button>
<button type="button" @click="$emit('close')">Cancel</button>
</form>
</div>
</Teleport>
</template>
<script setup>
import { reactive } from 'vue'
defineProps(['visible'])
const emit = defineEmits(['close', 'submit'])
const formData = reactive({})
function handleSubmit() {
emit('submit', formData)
}
</script>
```
```vue
<!-- ParentPage.vue -->
<template>
<button @click="showModal = true">Add User</button>
<!-- Events and slots work as expected despite teleportation -->
<ModalForm
:visible="showModal"
@close="showModal = false"
@submit="handleFormSubmit"
>
<template #default="{ formData }">
<input v-model="formData.name" placeholder="Name" />
<input v-model="formData.email" placeholder="Email" />
</template>
</ModalForm>
</template>
<script setup>
import { ref } from 'vue'
const showModal = ref(false)
function handleFormSubmit(data) {
console.log('Form submitted:', data)
showModal.value = false
}
</script>
```
## Vue Devtools Behavior
In Vue Devtools, teleported components appear under their logical parent:
```
App
└── ParentPage
└── ModalForm <-- Shows here (logical parent)
└── form content
```
Not under their DOM location:
```
// This is NOT how it appears in Devtools
body
└── ModalForm <-- Does NOT show here
```
## Reference
- [Vue.js Teleport - Component Hierarchy](https://vuejs.org/guide/built-ins/teleport.html#basic-usage)