JAVASCRIPT

Implementing a Reusable Modal Component with Vue 3 Teleport

Learn to create a flexible, reusable modal component in Vue 3 using the `Teleport` feature, ensuring proper DOM placement and accessibility for overlays.

// src/components/BaseModal.vue
<template>
  <teleport to="body">
    <transition name="modal-fade">
      <div v-if="isOpen" class="modal-overlay" @click.self="closeModal">
        <div class="modal-container">
          <div class="modal-header">
            <slot name="header">Default Header</slot>
            <button class="modal-close-button" @click="closeModal">&times;</button>
          </div>
          <div class="modal-body">
            <slot>Default Body Content</slot>
          </div>
          <div class="modal-footer">
            <slot name="footer">
              <button @click="closeModal">Close</button>
            </slot>
          </div>
        </div>
      </div>
    </transition>
  </teleport>
</template>

<script setup>
import { defineProps, defineEmits, watch } from 'vue';

const props = defineProps({
  isOpen: {
    type: Boolean,
    default: false,
  },
});

const emit = defineEmits(['update:isOpen']);

const closeModal = () => {
  emit('update:isOpen', false);
};

// Optional: Close modal on Escape key press
watch(() => props.isOpen, (newVal) => {
  if (newVal) {
    document.addEventListener('keydown', handleEscapeKey);
  } else {
    document.removeEventListener('keydown', handleEscapeKey);
  }
}, { immediate: true });

const handleEscapeKey = (e) => {
  if (e.key === 'Escape') {
    closeModal();
  }
};
</script>

<style scoped>
/* Basic Modal Styles */
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background-color: rgba(0, 0, 0, 0.6);
  display: flex;
  justify-content: center;
  align-items: center;
  z-index: 1000;
}

.modal-container {
  background: white;
  border-radius: 8px;
  padding: 20px;
  min-width: 300px;
  box-shadow: 0 4px 10px rgba(0, 0, 0, 0.2);
  position: relative;
}

.modal-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 15px;
  border-bottom: 1px solid #eee;
  padding-bottom: 10px;
}

.modal-close-button {
  background: none;
  border: none;
  font-size: 1.5rem;
  cursor: pointer;
}

.modal-footer {
  margin-top: 20px;
  border-top: 1px solid #eee;
  padding-top: 10px;
  text-align: right;
}

/* Transition Styles */
.modal-fade-enter-active, .modal-fade-leave-active {
  transition: opacity 0.3s ease;
}
.modal-fade-enter-from, .modal-fade-leave-to {
  opacity: 0;
}
</style>

// src/App.vue (example usage)
<template>
  <div>
    <h1>Vue 3 Teleport Modal Example</h1>
    <button @click="isModalOpen = true">Open Modal</button>

    <BaseModal v-model:isOpen="isModalOpen">
      <template #header>
        <h2>Custom Modal Title</h2>
      </template>
      <p>This is the content of my beautiful modal. It uses <strong>Teleport</strong>!</p>
      <template #footer>
        <button @click="isModalOpen = false">Close from Footer</button>
        <button @click="alert('Action performed!')">Perform Action</button>
      </template>
    </BaseModal>
  </div>
</template>

<script setup>
import { ref } from 'vue';
import BaseModal from './components/BaseModal.vue';

const isModalOpen = ref(false);
</script>
How it works: This snippet demonstrates how to build a reusable modal component using Vue 3's `Teleport` feature. `Teleport` allows you to render a component's content into a different part of the DOM, even if that part is outside the component's parent hierarchy. By using `to="body"`, the modal content is rendered directly under the `<body>` tag, preventing CSS stacking context issues (z-index) and ensuring it always appears on top. The modal also includes slots for flexible content, basic styling, and a transition for a smooth open/close animation, along with keyboard accessibility for closing with the Escape key.

Need help integrating this into your project?

Our team of expert developers can help you build your custom application from scratch.

Hire DigitalCodeLabs