JAVASCRIPT

Create a Reusable Modal Component with Vue 3 Teleport

Build an accessible, reusable modal component in Vue 3 that teleports its content to a separate DOM location, ensuring proper layering and accessibility.

// components/Modal.vue
<template>
  <teleport to="body">
    <transition name="modal-fade">
      <div v-if="isOpen" class="modal-overlay" @click.self="$emit('close')">
        <div class="modal-container">
          <header class="modal-header">
            <slot name="header">
              <h3>Default Header</h3>
            </slot>
            <button class="modal-close-button" @click="$emit('close')">&times;</button>
          </header>
          <section class="modal-body">
            <slot>
              <p>Default Body Content</p>
            </slot>
          </section>
          <footer class="modal-footer">
            <slot name="footer">
              <button @click="$emit('close')">Close</button>
            </slot>
          </footer>
        </div>
      </div>
    </transition>
  </teleport>
</template>

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

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

const emit = defineEmits(['close']);
</script>

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

.modal-container {
  background: white;
  padding: 20px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
  width: 90%;
  max-width: 500px;
  transform: translateY(0);
  transition: transform 0.3s ease;
}

.modal-header, .modal-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  margin-bottom: 10px;
}

.modal-close-button {
  background: none;
  border: none;
  font-size: 1.5em;
  cursor: pointer;
  line-height: 1;
  padding: 0 5px;
}

/* 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;
}

.modal-fade-enter-active .modal-container,
.modal-fade-leave-active .modal-container {
  transition: transform 0.3s ease;
}

.modal-fade-enter-from .modal-container,
.modal-fade-leave-to .modal-container {
  transform: translateY(-50px);
}
</style>

/* Usage in parent component:
<template>
  <button @click="isModalOpen = true">Open Modal</button>
  <Modal :is-open="isModalOpen" @close="isModalOpen = false">
    <template #header><h2>My Custom Modal Title</h2></template>
    <p>This is the main content of my modal.</p>
    <template #footer><button @click="isModalOpen = false">Cancel</button></template>
  </Modal>
</template>

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

const isModalOpen = ref(false);
</script>
*/
How it works: This snippet illustrates how to build a flexible and reusable modal component using Vue 3's `Teleport` feature. `Teleport` allows the modal's DOM structure to be rendered directly into a target element (like the `<body>`), preventing z-index issues and ensuring proper layering regardless of its parent component's position. It also utilizes named and default slots for customizable content injection and Vue's built-in `<transition>` component for smooth opening and closing animations, enhancing the user experience.

Need help integrating this into your project?

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

Hire DigitalCodeLabs