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>
134 lines
4.1 KiB
Markdown
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)
|