← Back to all snippets
JAVASCRIPT

Reusable Modal Component with Teleport and Slots

Build a versatile and accessible modal component in Vue 3, leveraging `Teleport` to render outside the DOM hierarchy and `slots` for flexible content.

// ModalDialog.vue
<template>
  <Teleport to="body">
    <div v-if="isOpen" class="modal-backdrop" @click.self="closeModal">
      <div class="modal-content">
        <header class="modal-header">
          <slot name="header"><h2>Default Header</h2></slot>
          <button class="close-button" @click="closeModal">X</button>
        </header>
        <main class="modal-body">
          <slot>Default Body Content</slot>
        </main>
        <footer class="modal-footer">
          <slot name="footer"></slot>
        </footer>
      </div>
    </div>
  </Teleport>
</template>

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

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

const emit = defineEmits(['close']);

const closeModal = () => {
  emit('close');
};
</script>

<style scoped>
.modal-backdrop {
  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-content {
  background: white;
  padding: 20px;
  border-radius: 8px;
  min-width: 300px;
  max-width: 90%;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  display: flex;
  flex-direction: column;
}

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

.modal-header h2 {
  margin: 0;
}

.close-button {
  background: none;
  border: none;
  font-size: 1.2em;
  cursor: pointer;
}

.modal-footer {
  margin-top: 15px;
  text-align: right;
}
</style>

// How to use in ParentComponent.vue:
// <template>
//   <div>
//     <button @click="showModal = true">Open Modal</button>
//     <ModalDialog :is-open="showModal" @close="showModal = false">
//       <template #header><h3>Custom Modal Title</h3></template>
//       <p>This is dynamic content passed through the default slot.</p>
//       <template #footer>
//         <button @click="showModal = false">Confirm</button>
//       </template>
//     </ModalDialog>
//   </div>
// </template>
//
// <script setup>
// import { ref } from 'vue';
// import ModalDialog from './ModalDialog.vue';
//
// const showModal = ref(false);
// </script>
How it works: This snippet provides a robust and reusable modal component in Vue 3. It utilizes the `Teleport` component to render the modal's content directly into the `body` of the HTML document, ensuring it's not affected by parent component styling or z-index issues. `Slots` are extensively used (default, named `header`, `footer`) to allow the parent component to inject highly flexible content. The modal's visibility is controlled by the `isOpen` prop, and it emits a `close` event when the backdrop or close button is clicked, enabling easy integration and control from any parent component.

Need help integrating this into your project?

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

Hire DigitalCodeLabs