Files
agent-skills/skills/vue-best-practices/reference/teleport-css-positioning-issues.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

134 lines
4.1 KiB
Markdown

---
title: Use Teleport to Avoid CSS Positioning Issues
impact: MEDIUM
impactDescription: Modals and overlays can break when parent elements have transform, perspective, or filter CSS properties
type: best-practice
tags: [vue3, teleport, modal, css, z-index, positioning]
---
# Use Teleport to Avoid CSS Positioning Issues
**Impact: MEDIUM** - CSS `position: fixed` only positions relative to the viewport when no ancestor has `transform`, `perspective`, or `filter` properties. Without Teleport, modals nested deep in the DOM can break when parent elements use these CSS properties.
This is a common issue when using CSS animations on parent elements or when modals are deeply nested in component hierarchies.
## Task Checklist
- [ ] Use `<Teleport>` for modals, tooltips, and other fixed-position overlays
- [ ] Teleport to a container outside the Vue app hierarchy (e.g., `body` or `#modals`)
- [ ] Avoid relying on `z-index` alone for stacking - teleport ensures proper layering
**Problem - Without Teleport:**
```vue
<template>
<div class="animated-container">
<!-- Modal is nested inside animated parent -->
<button @click="showModal = true">Open Modal</button>
<!-- BROKEN: position: fixed is relative to .animated-container, not viewport -->
<div v-if="showModal" class="modal">
<p>This modal will be mispositioned!</p>
</div>
</div>
</template>
<style>
.animated-container {
/* This breaks position: fixed in descendants */
transform: translateX(0);
/* Or any of these: */
/* perspective: 1000px; */
/* filter: blur(0); */
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999; /* z-index constrained by parent stacking context */
}
</style>
```
**Solution - With Teleport:**
```vue
<template>
<div class="animated-container">
<button @click="showModal = true">Open Modal</button>
<!-- Teleport moves modal outside the problematic parent -->
<Teleport to="body">
<div v-if="showModal" class="modal">
<p>Modal now positions correctly relative to viewport!</p>
<button @click="showModal = false">Close</button>
</div>
</Teleport>
</div>
</template>
<style>
.animated-container {
transform: translateX(0); /* No longer affects modal */
}
.modal {
position: fixed;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
z-index: 9999; /* z-index now works as expected */
}
</style>
```
## Z-Index Stacking Context Issues
Even without transform/filter, deeply nested elements create stacking contexts that constrain z-index:
```vue
<template>
<div class="sidebar" style="z-index: 100; position: relative;">
<div class="nested-component">
<!-- Without Teleport: z-index: 9999 is constrained within sidebar -->
<div v-if="showDropdown" class="dropdown" style="z-index: 9999;">
This dropdown may be covered by other elements
</div>
</div>
</div>
<div class="content" style="z-index: 200; position: relative;">
<!-- This will cover the dropdown because parent z-index is higher -->
</div>
</template>
```
**Solution:**
```vue
<template>
<div class="sidebar">
<div class="nested-component">
<Teleport to="body">
<div v-if="showDropdown" class="dropdown">
Dropdown now renders at body level - no z-index constraints
</div>
</Teleport>
</div>
</div>
</template>
```
## When to Use Teleport
| UI Element | Should Teleport? | Reason |
|------------|-----------------|--------|
| Full-screen modals | Yes | Fixed positioning, need to escape stacking contexts |
| Tooltips | Often | May need to escape overflow: hidden containers |
| Dropdowns | Sometimes | Depends on container overflow/positioning |
| Notifications/toasts | Yes | Should appear above all content |
| Inline popups | Usually no | Position relative to trigger element |
## Reference
- [Vue.js Teleport - Basic Usage](https://vuejs.org/guide/built-ins/teleport.html#basic-usage)
- [MDN - Stacking Context](https://developer.mozilla.org/en-US/docs/Web/CSS/CSS_positioned_layout/Understanding_z-index/Stacking_context)