How to Build Modals in VueJS 3 with vue-final-modal and Tailwind 3
Out of all the things I’ve tried to create, modals have been one of the most frustrating. As someone who isn’t well-versed in front-end development, I had no idea where to begin with building them.
While I believe that vue-final-modal
is the top choice for a modal library in VueJs, I won’t be comparing it with other solutions here. Rather, I’d like to share my experience and offer guidance on how to create modals in an easy and effective manner.
Let’s get started:
npm install vue-final-modal
Add it into your project (main.js
):
import { createApp } from 'vue'
import { createVfm } from 'vue-final-modal'
import App from './App.vue'
const app = createApp(App)
const vfm = createVfm()
app.use(vfm).mount('#app')
Finally, add the ModalsContainer
in the App.vue
file:
<template>
<!-- // -->
<ModalsContainer />
</template>
<script setup>
import { ModalsContainer } from 'vue-final-modal'
</script>
In my project, I have organized the modals by placing them into a folder aptly named modals
.
Feel free to create the modals
folder in your project wherever you see fit. Personally, I have placed mine in components/modals
, but the location is entirely up to your preference.
For the next step, you’ll need to create a Vue component with the name Modal.vue
. However, it’s entirely you’re up to you whether you want to stick with this name or choose a different one. This file will act as the primary Modal component for other modals.
Did I say other modals?
Indeed, I create a separate modal component for every modal, which helps me keep them organized and under control.
For example, I have a modal named CreateNewFolderModal.vue
, and so on.
Here is the Modal.vue
content:
<template>
<VueFinalModal
:click-to-close="closable"
:esc-to-close="closable"
@opened="opened"
class="flex justify-center items-center"
content-class="flex flex-col max-w-xl mx-4 p-6 bg-white border rounded-lg space-y-2 shadow-xl"
>
<div>
<div class="mt-3 text-center sm:mt-5">
<h3 class="text-base font-bold text-xl leading-6 text-gray-900">
{{ title }}
</h3>
<div class="mt-2">
<slot name="content"/>
</div>
</div>
</div>
</VueFinalModal>
</template>
<script setup>
import { VueFinalModal } from "vue-final-modal";
defineProps({
title: {
type: String,
required: true,
},
closable: {
type: Boolean,
required: false,
default: true,
},
opened: {
type: Function,
required: false,
}
});
</script>
There is one slot
and a few properties:
-
title
: The modal’s title, such as create a new folder. -
closable
: determines whether the modal is permitted to close or not. For instance, during an ongoingaxios
request, I prefer to prevent the user from closing the modal until the http request completes. This option utilizes theesc-to-close
andclick-to-close
options invue-final-modal
. -
opened
: this option utilizes theopened
event fromvue-final-modal
giving me more control when the modal becomes visible. For example, I utilize this option to set the focus on theinput
.
You are not restricted to these options, so feel free to instruct the documentation and add any additional options that meet your needs.
Please note that I have added the
title
as a property and not aslot
. This approach satisfies my requirements, but you may find it necessary to switch to using aslot
. So, feel free to make that change if you see fit.
Let’s create the actual modal, here is one of the modals in my project CreateNewFolderModal.vue
:
<template>
<Modal title="Create new folder" :closable="closeable" :opened="focusInput">
<template #content>
<form @submit.prevent="create">
<div class="mt-2">
<input class="input w-80" name="folderName" id="folderName" v-model="folderName" ref="folderNameInputRef" placeholder="Folder name" :disabled="isCreating"/>
</div>
<div class="mt-2">
<button class="btn" type="submit" :disabled="isCreating">Create</button>
</div>
</form>
</template>
</Modal>
</template>
<script setup>
import Modal from "@/components/Modal.vue";
import { ref } from "vue";
import folders from "@/api/folders";
const closeable = ref(true);
const isCreating = ref(false);
const folderNameInputRef = ref();
const folderName = ref();
const emit = defineEmits(['created']);
function create() {
closeable.value = false;
isCreating.value = true;
folders.createFolder(folderName.value).then((data) => {
closeable.value = true;
isCreating.value = false;
emit('created', data)
}).catch(() => {
closeable.value = true;
isCreating.value = false;
// Handle the errors
})
}
function focusInput() {
folderNameInputRef.value.focus();
}
</script>
This component is quite straightforward as it uses the Modal
component and emits a created
event once the folder is created. There isn’t much else to it.
It’s worth noting that
vue-final-modal
operates on events, which implies that you need to emit an event to control it later on.
The last step it to use the modal in your project.
Here is how I use CreateNewFolderModal.vue
in my project:
<template>
<div>
<button type="button" @click="openCreateNewFolder">Create new folder</button>
</div>
</template>
<script setup>
import { useModal } from "vue-final-modal";
import CreateNewFolderModal from "@/components/modals/CreateNewFolderModal.vue";
const { open: openCreateNewFolder, close: closeCreateNewFolder } = useModal({
component: CreateNewFolderModal,
attrs: {
onCreated(folder) {
// ...
closeCreateNewFolder();
},
},
});
</script>
Let’s demystify the code.
The useModal
function provides the open
and close
functions, which are utilized to show and hide the modal, respectively.
To improve the code’s readability, I assigned openCreateNewFolder
and closeCreateNewFolder
as aliases for the open
and close
functions. This simple technique enhances the code’s clarity.
In the component
property, we specify the modal component to use.
To handle the created
event, I utilize the attrs
property, which is provided by vue-final-modal
. Using on
and specifying the event name is all that’s required.
Another fascinating option available in useModal
is slots
, which enables you to modify the slots within your modal.
Please refer to the
vue-final-modal
documentation in order to know more about the use ofuseModal
.
That’s it. Thanks for reading.