brand logo

ドキュメント

Teleport 実例:#teleports へモーダルを描画する最小構成

Nuxt で <Teleport> を使い、SSR 対応の #teleports ターゲットへモーダルを描画する実例と、任意セレクタへのクライアント限定テレポート例。

<Teleport>to は CSS セレクタ文字列または DOM ノードを受け取ります。Nuxt では SSR 時に #teleports へのテレポートのみサポートされます。その他のターゲットへ描画したい場合は、<ClientOnly> でクライアント専用にしてください。

何を作るか

  • ページ内のボタンで モーダルを開く
  • モーダル本体は <Teleport to="#teleports">DOM の末尾(#teleports)へ描画
  • 背景クリックで閉じる・閉じるボタンも用意

Nuxt の既定テンプレートには #teleports が含まれています。もし app.html をカスタムしている場合は、以下のように #teleports を必ず含めてください。

app.html
<!DOCTYPE html>
<html {{ HTML_ATTRS }}>
  <head {{ HEAD_ATTRS }}>
    {{ HEAD }}
  </head>
  <body {{ BODY_ATTRS }}>
    {{ APP }}
    <!-- ✅ Nuxt がテレポート先として参照する領域 -->
    <div id="teleports"></div>
  </body>
</html>

実装(最小)

<template>
  <Teleport to="#teleports">
    <div
      v-if="open"
      class="modal-backdrop"
      @click.self="emit('update:open', false)"
    >
      <div class="modal-panel" role="dialog" aria-modal="true">
        <header class="modal-header">
          <h2 class="modal-title">テレポートされたモーダル</h2>
          <button class="icon-btn" @click="emit('update:open', false)" aria-label="閉じる">×</button>
        </header>
        <div class="modal-body">
          <p>こんにちは!この要素は <code>#teleports</code> へ描画されています。</p>
        </div>
        <footer class="modal-footer">
          <button class="btn" @click="emit('update:open', false)">閉じる</button>
        </footer>
      </div>
    </div>
  </Teleport>
</template>

<script setup lang="ts">
const props = defineProps<{ open: boolean }>()
const emit = defineEmits<{ (e: 'update:open', v: boolean): void }>()
</script>

<style scoped>
.modal-backdrop {
  position: fixed; inset: 0;
  background: rgba(15, 23, 42, .55);
  display: grid; place-items: center;
  z-index: 50;
}
.modal-panel {
  width: min(92vw, 560px);
  background: #fff;
  border-radius: 12px;
  box-shadow: 0 20px 40px rgba(0,0,0,.2);
  overflow: hidden;
}
.modal-header, .modal-footer { padding: 12px 16px; }
.modal-title { margin: 0; font-size: 18px; }
.modal-body { padding: 0 16px 16px; }
.icon-btn {
  border: none; background: transparent; font-size: 18px; cursor: pointer;
}
.btn {
  appearance: none;
  padding: 8px 14px; border-radius: 8px;
  background: #111827; color: #fff; border: 1px solid #111827;
}
.btn:hover { opacity: .9; }
</style>

別ターゲットへ「クライアント限定」テレポート

#teleports 以外に出したい時は <ClientOnly> で包めば OK(SSR は行いません)。

<template>
  <div class="layout">
    <aside id="sidebar" class="sidebar"><!-- サイドバー --></aside>
    <main><slot /></main>
  </div>
</template>

<style scoped>
.layout { display: grid; grid-template-columns: 280px 1fr; min-height: 100vh; }
.sidebar { border-right: 1px solid #e5e7eb; padding: 16px; }
</style>

関連ドキュメント