11.02.2026

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 body altına gidebilir.
  • overflow: hidden olan 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 overflow yeterli 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.