JAVASCRIPT

Build a Reusable Modal Component with Vue 3 Slots

Create a highly flexible and reusable modal component in Vue 3 using named and scoped slots, allowing dynamic content injection for headers, bodies, and footers.

// components/BaseModal.vue
<template>
  <div v-if="isOpen" class="modal-overlay" @click.self="closeModal">
    <div class="modal-container">
      <div class="modal-header">
        <slot name="header">
          <h3>Default Header</h3>
        </slot>
        <button @click="closeModal" class="modal-close-button">&times;</button>
      </div>
      <div class="modal-body">
        <slot name="body">
          <p>Default modal body content.</p>
        </slot>
      </div>
      <div class="modal-footer">
        <slot name="footer" :close="closeModal">
          <button @click="closeModal">Close</button>
        </slot>
      </div>
    </div>
  </div>
</template>

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

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

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

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

<style scoped>
.modal-overlay {
  position: fixed;
  top: 0;
  left: 0;
  width: 100%;
  height: 100%;
  background: 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;
  min-width: 300px;
  box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
  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.5em;
  cursor: pointer;
}
.modal-footer {
  margin-top: 20px;
  border-top: 1px solid #eee;
  padding-top: 10px;
  text-align: right;
}
</style>

<!-- App.vue (Usage Example) -->
<template>
  <div>
    <button @click="isModalOpen = true">Open Custom Modal</button>
    <BaseModal :is-open="isModalOpen" @close="isModalOpen = false">
      <template #header>
        <h2>My Custom Modal Title</h2>
      </template>
      <template #body>
        <p>This is the custom body content for my modal. It can contain any HTML.</p>
        <input type="text" placeholder="Enter something" />
      </template>
      <template #footer="{ close }">
        <button @click="handleConfirm">Confirm</button>
        <button @click="close">Cancel</button>
      </template>
    </BaseModal>
  </div>
</template>

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

const isModalOpen = ref(false);

const handleConfirm = () => {
  alert('Confirmed!');
  isModalOpen.value = false;
};
</script>
How it works: This snippet creates a versatile `BaseModal` component using Vue 3's slot system. It utilizes named slots (`#header`, `#body`, `#footer`) to allow consumers to inject custom content into different sections of the modal, with default fallback content provided. A scoped slot in the footer (`#footer="{ close }"`) demonstrates how to pass data or methods (like `closeModal`) back to the consuming component, making the component highly flexible and reusable.

Need help integrating this into your project?

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

Hire DigitalCodeLabs