Vue 3’te Teleport ile Modal ve Bildirimleri DOM’dan Bağımsız Yönetme
Teleport ile modal, toast ve dropdown gibi katmanları doğru yerde render ederek CSS ve z-index savaşlarını bitirin.
Modern arayüzlerde modal, toast, dropdown gibi “üst katman” bileşenleri en çok z-index, overflow: hidden ve container hiyerarşisi yüzünden sorun çıkarır. Vue 3’ün Teleport özelliği bu tip bileşenleri, mantıksal olarak bir yerde yönetip DOM’da bambaşka bir yere (genellikle body altına) taşımayı sağlar.
Aşağıda, Teleport’u gerçek hayatta sık yaşanan problemleri çözecek şekilde ele alalım.
Teleport nedir, ne zaman gerekir?
Teleport, bir bileşenin template çıktısını hedef bir DOM düğümüne taşır:
- Modal içerik komponent içinde kalsa bile DOM’da
bodyaltına gidebilir. overflow: hiddenolan bir kart içinde modal açıldığında kesilmez.- Karmaşık layout’larda z-index kavgaları azalır.
Mantık bileşende kalır, render hedefi taşınır.
1) Basit bir modalı Teleport ile “body” altına almak
Hedef container
index.html içine bir portal kökü eklemek iyi pratiktir:
<body>
<div id="app"></div>
<div id="overlays"></div>
</body>
Modal bileşeni (BaseModal.vue)
<script setup>
import { watch, onBeforeUnmount } from 'vue'
const props = defineProps({
modelValue: { type: Boolean, default: false },
closeOnEsc: { type: Boolean, default: true }
})
const emit = defineEmits(['update:modelValue'])
function close() {
emit('update:modelValue', false)
}
function onKeydown(e) {
if (!props.closeOnEsc) return
if (e.key === 'Escape' && props.modelValue) close()
}
watch(() => props.modelValue, (open) => {
// Scroll kilidi (basit yaklaşım)
document.body.style.overflow = open ? 'hidden' : ''
})
document.addEventListener('keydown', onKeydown)
onBeforeUnmount(() => {
document.removeEventListener('keydown', onKeydown)
document.body.style.overflow = ''
})
</script>
<template>
<Teleport to="#overlays">
<div v-if="modelValue" class="backdrop" @click.self="close">
<div class="modal" role="dialog" aria-modal="true">
<header class="header">
<slot name="title" />
<button class="x" @click="close" aria-label="Close">×</button>
</header>
<section class="content">
<slot />
</section>
</div>
</div>
</Teleport>
</template>
<style scoped>
.backdrop{
position:fixed; inset:0; background:rgba(0,0,0,.5);
display:flex; align-items:center; justify-content:center;
}
.modal{ background:#fff; width:min(560px, 92vw); border-radius:12px; overflow:hidden; }
.header{ display:flex; justify-content:space-between; align-items:center; padding:12px 16px; }
.content{ padding:16px; }
.x{ border:none; background:transparent; font-size:22px; cursor:pointer; }
</style>
Kullanım
<script setup>
import { ref } from 'vue'
import BaseModal from './BaseModal.vue'
const open = ref(false)
</script>
<template>
<button @click="open = true">Detayları Aç</button>
<BaseModal v-model="open">
<template #title>Ödeme Bilgisi</template>
<p>Bu içerik DOM’da #overlays içine taşındı.</p>
</BaseModal>
</template>
2) “Aynı sayfada birden çok modal” ve katman sırası
Teleport tek başına “hangi modal üstte?” sorusunu çözmez. İki pratik yaklaşım:
- Tek bir modal yöneticisi (store ile) kullanıp sıraya almak
- Her modal açılışında z-index’i artırmak
En yalın yöntem: modal’ları tek bir #overlays altında render ettiğiniz için, DOM sırası da önem kazanır. “Son açılan en üstte” için modal’ı liste üzerinden en sona eklemek genellikle yeterlidir.
3) Toast/Bildirimleri Teleport ile layout’tan ayırmak
Toast’lar sayfa akışını bozmamalı. Teleport burada da temiz çözüm sunar:
<template>
<Teleport to="#overlays">
<div class="toasts">
<div v-for="t in toasts" :key="t.id" class="toast">
{{ t.message }}
</div>
</div>
</Teleport>
</template>
<style scoped>
.toasts{ position:fixed; right:16px; top:16px; display:flex; flex-direction:column; gap:8px; }
.toast{ background:#111; color:#fff; padding:10px 12px; border-radius:10px; opacity:.95; }
</style>
Böylece toast’lar, hangi sayfada/komponentte tetiklenirse tetiklensin, her zaman sabit konumda görünür.
Dikkat edilmesi gerekenler
- SSR (Nuxt): Teleport hedefi server’da yoksa hydration uyarıları çıkabilir. Çözüm: sadece client’ta render etmek (
<ClientOnly>gibi) veya hedefi layout’ta garanti etmek. - Erişilebilirlik: Modal açılınca focus yönetimi (focus trap) ve aria etiketleri önemlidir.
- Scroll kilidi: Basit
body overflowyeterli olmayabilir; iOS gibi ortamlarda daha özel çözümler gerekebilir.
Sonuç
Teleport; modal, toast, dropdown gibi üst katman bileşenlerini DOM hiyerarşisinden kurtararak daha öngörülebilir bir UI sağlar. Daha az CSS hack’i, daha az z-index kaosu, daha temiz bir component mimarisi.