I want to use headless ui modal in vue3 but the problem is I don't understand how I can toggle the modal from parent (App.vue) if the modal is a component.
I tried to pass a prop in the Modal.vue but it's not working, my strategy was to use a modalActive prop and watch it and call appropriate function to toggle the modal, but it's simply not working.
The example in headless UI uses a button but that's inside the component it self so obviously it can access functions of that component without problem.
My code:
App.vue
<template>
<Modal>
<template v-slot:default>
<LoginForm />
</template>
</Modal>
</template>
<script setup lang="ts">
import Modal from './components/Modal.vue';
import LoginForm from './components/LoginForm.vue';
import { ref } from 'vue';
</script>
Modal.vue
<template>
<TransitionRoot appear :show="isOpen" as="template">
<Dialog as="div" @close="closeModal">
<div class="fixed inset-0 z-10 overflow-y-auto">
<div class="min-h-screen px-4 text-center">
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0"
enter-to="opacity-100"
leave="duration-200 ease-in"
leave-from="opacity-100"
leave-to="opacity-0"
>
<DialogOverlay class="fixed inset-0" />
</TransitionChild>
<span class="inline-block h-screen align-middle" aria-hidden="true">​</span>
<TransitionChild
as="template"
enter="duration-300 ease-out"
enter-from="opacity-0 scale-95"
enter-to="opacity-100 scale-100"
leave="duration-200 ease-in"
leave-from="opacity-100 scale-100"
leave-to="opacity-0 scale-95"
>
<div
class="inline-block w-full max-w-md p-6 my-8 overflow-hidden text-left align-middle transition-all transform bg-white shadow-xl rounded-2xl"
>
<slot></slot> <!-- slot for forms -->
</div>
</TransitionChild>
</div>
</div>
</Dialog>
</TransitionRoot>
</template>
<script setup lang="ts">
import { ref, watch } from 'vue'
import {
TransitionRoot,
TransitionChild,
Dialog,
DialogOverlay,
DialogTitle,
} from '@headlessui/vue'
const isOpen = ref(true)
function closeModal() {
isOpen.value = false
}
function openModal() {
isOpen.value = true
}
</script>
You should use defineExpose
together with $refs
.
<script setup>
import { ref, watch, defineExpose } from 'vue'
...
const closeModal = function() {
isOpen.value = false
}
const openModal = function() {
isOpen.value = true
}
defineExpose({
openModal,
closeModal
})
</script>
Template reference to your component:
<Modal ref="modal">
And the method openModal
in the Vue App:
components: {
Modal
},
methods: {
openModal() {
this.$refs.modal.openModal();
}
}
Here is a working version: Vue SFC Playground
Here are the important parts of Vue 3 Documentation to understand the solution:
<script setup>
Tolbxela's answer works great, but if you're using the composition api, the final part of the answer will look a little different.
const modal = ref(null)
//the variable name (modal) needs to match the template ref name
//given in the second step of Tolbxela's answer
const openModal = () => {
modal.value.openModal()
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With