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')">×</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.