Vue3+composition API: Creating a Reusable Modal Component with outside click handling.

Roman Vinnick
3 min readAug 23, 2023

--

Hi, today I wanna show how I create reusable modal windows (popups) in Vue3 with VueUse library. However the usage is not really necessary, it just makes code simpler and more concise.

But let me briefly describe the user story here:

Why Outside Click Handling in Modal Windows Matters

Modal windows are a core element of modern web design, offering an unobtrusive means to provide additional information or gather user input. However, a common user frustration arises when closing these modals. Often, users must locate and interact with a close button within the modal, which can be cumbersome and disrupt their flow.

Seamless Interaction

Outside click handling eliminates the need to precisely target a small close button within the modal.

Intuitive Behavior

Long story short: users instinctively understand that interacting with the background which is distinct from the modal actually dismisses one.

Mobile-Friendly Design

On mobile devices with limited screen space, an accessible method to close modals is vital. Outside click handling provides a touch-friendly mechanism to dismiss modals without users needing to pinpoint tiny close buttons.

Building the Modal Component

Let’s start by building a reusable modal component, let’s say it’s named ModalComponent.vue :

<script setup>
import { defineProps, defineEmits, ref } from "vue";
import {onClickOutside} from '@vueuse/core'

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

const emit = defineEmits(["modal-close"]);

const target = ref(null)
onClickOutside(target, ()=>emit('modal-close'))

</script>

<template>
<div v-if="isOpen" class="modal-mask">
<div class="modal-wrapper">
<div class="modal-container" ref="target">
<div class="modal-header">
<slot name="header"> default header </slot>
</div>
<div class="modal-body">
<slot name="content"> default content </slot>
</div>
<div class="modal-footer">
<slot name="footer">
<div>
<button @click.stop="emit('modal-close')">Submit</button>
</div>
</slot>
</div>
</div>
</div>
</div>
</template>

<style scoped>
.modal-mask {
position: fixed;
z-index: 9998;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
}
.modal-container {
width: 300px;
margin: 150px auto;
padding: 20px 30px;
background-color: #fff;
border-radius: 2px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.33);
}

</style>

In this code, we’re utilizing the onClickOutside function from @vueuse/core to detect clicks outside the modal. When such a click is detected, we emit the "modal-close" event, allowing the parent component to handle modal closure. So basically this functionality is handled from within.

And now the parent component for our modal, Test.vue :

<script setup>
import { ref } from "vue";
import ModalComponent from "../components/ModalComponent.vue";

const isModalOpened = ref(false);

const openModal = () => {
isModalOpened.value = true;
};
const closeModal = () => {
isModalOpened.value = false;
};

const submitHandler = ()=>{
//here you do whatever
}
</script>

<template>
<div>
<button @click="openModal">Open generic modal</button>
</div>
<ModalComponent :isOpen="isModalOpened" @modal-close="closeModal" @submit="submitHandler" name="first-modal">
<template #header>Custom header</template>
<template #content>Custom content</template>
<template #footer>Custom content</template>
</ModalComponent>
</template>

This setup allows us to use the ModalComponent.vue as a flexible and reusable component, with customized content and an elegant way to close it using the outside click feature.

It also has slots for custom content, as well as custom footer section, which is meant to contain buttons.

This is how it looks like without slots usage:

and of course how it’s done:

<template>
<div>
<button @click="openModal">Open generic modal</button>
</div>
<ModalComponent :isOpen="isModalOpened" @modal-close="closeModal" @submit="submitHandler" name="first-modal"/>
</template>

Thanks! And happy coding!

--

--

Roman Vinnick

Tech writer decoding the latest in coding & innovation. Passionate about making tech accessible to all. Join my journey of discovery in the world of technology.