Skip to main content
Vuetify0 v1.0 is here
Vuetify0 Logo
Theme
Mode
Palettes
Accessibility
Vuetify One
Sign in to Vuetify One

Access premium tools across the Vuetify ecosystem — Bin, Play, Studio, and more.

Not a subscriber? See what's included

Portal

Renderless teleport wrapper with automatic z-index stacking.

Edit this page
Report a Bug
Open issues
View on GitHub
Copy Markdown

PreviewRenderlessIntermediateAug 3, 2026

Usage

Portal wraps Vue’s <Teleport> with automatic useStack integration. Content is teleported to body by default and receives a zIndex via slot props for proper overlay ordering.

Mobile — renders inline below
<script setup lang="ts">
  import { Portal, useBreakpoints } from '@vuetify/v0'
  import { shallowRef } from 'vue'

  const show = shallowRef(false)
  const { smAndDown: mobile } = useBreakpoints()
</script>

<template>
  <div class="flex flex-col gap-4">
    <div class="flex items-center gap-4">
      <button
        class="rounded bg-primary px-4 py-2 text-on-primary"
        @click="show = true"
      >
        Show overlay
      </button>

      <span class="text-sm text-on-surface-variant">
        {{ mobile ? 'Mobile — renders inline below' : 'Desktop — teleported to body' }}
      </span>
    </div>

    <Portal v-if="show" :disabled="mobile" @close="show = false">
      <template #default="{ zIndex, close }">
        <div
          class="rounded-lg bg-surface-variant p-4 shadow-lg"
          :class="mobile ? '' : 'fixed bottom-4 right-4'"
          :style="mobile ? {} : { zIndex }"
        >
          <div class="flex items-start justify-between gap-4">
            <div>
              <p class="text-sm font-medium">Portal Content</p>

              <p class="mt-1 text-xs text-on-surface-variant">
                z-index: {{ zIndex }} · {{ mobile ? 'inline' : 'teleported' }}
              </p>
            </div>

            <button
              class="rounded px-2 py-1 text-xs text-on-surface-variant hover:bg-surface hover:text-on-surface"
              @click="close"
            >
              close
            </button>
          </div>
        </div>
      </template>
    </Portal>
  </div>
</template>

Anatomy

vue
<script setup lang="ts">
  import { Portal } from '@vuetify/v0'
</script>

<template>
  <Portal />
</template>

Accessibility

Portal is transparent — it adds no DOM elements, ARIA attributes, or keyboard behavior. Accessibility is the responsibility of the content you teleport.

Tip

When teleporting interactive content (modals, menus, notifications), ensure it has proper ARIA roles, keyboard handling, and focus management. Portal handles where content renders, not how it behaves.

Landmarks

Teleported content lands outside your app’s landmark structure — body is not inside <main>, so audit tools flag the subtree with the axe region↗︎ rule. Give the teleported subtree its own semantics instead of relying on the surrounding page:

  • role="dialog" — dialogs are exempt from the landmark rule

  • role="status" or role="alert" — toast and notification regions

  • role="region" plus aria-label — arbitrary overlays with no more specific role

vue
<template>
  <Portal>
    <div role="region" aria-label="Chat panel">...</div>
  </Portal>
</template>

FAQ

Discord
Need help? Join our community for support and discussions ↗

API Reference

The following API details are for all variations of the Portal component.
Was this page helpful?

© 2016-1970 Vuetify, LLC
Services
Ctrl+/