Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

phillipvarsted's avatar

InertiaJS / Vue Composition API Modal

Hello world!

I am trying to create a reusable modal component with headless ui's modal component. So far I've created the component and placed it in the page that needs the modal.

I have defined the expose in the child component as so:

TheModal.vue / Child Component

<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"> &#8203; </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">
							<DialogTitle as="h3" class="text-lg font-medium leading-6 text-gray-900"> Payment successful </DialogTitle>
							<div class="mt-2">
								<p class="text-sm text-gray-500">Your payment has been successfully submitted. We’ve sent you an email with all of the details of your order.</p>
							</div>

							<div class="mt-4">
								<button
									type="button"
									class="inline-flex justify-center px-4 py-2 text-sm font-medium text-blue-900 bg-blue-100 border border-transparent rounded-md hover:bg-blue-200 focus:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:ring-blue-500"
									@click="closeModal"
								>
									Got it, thanks!
								</button>
							</div>
						</div>
					</TransitionChild>
				</div>
			</div>
		</Dialog>
	</TransitionRoot>
</template>

<script setup>
import { ref } from 'vue';
import { TransitionRoot, TransitionChild, Dialog, DialogOverlay, DialogTitle } from '@headlessui/vue';

const isOpen = ref(false);

function closeModal() {
	isOpen.value = false;
}
function openModal() {
	isOpen.value = true;
}

defineExpose({
    openModal,
    closeModal
})
</script>

Index.vue / Parent component

<template>
	<AuthBase>
		<template #title>Brugere</template>

		... more irrelevant code ...

		<ThePagination :links="users.meta.links" :meta="users.meta" />
	    <TheModal ref="modal"></TheModal>
	</AuthBase>
</template>

<script setup>
import { Menu, MenuButton, MenuItem, MenuItems } from '@headlessui/vue';
import { ChevronDownIcon } from '@heroicons/vue/solid';
import { Inertia } from '@inertiajs/inertia';
import { ref, watch } from '@vue/runtime-core';
import debounce from 'lodash/debounce';

let props = defineProps({
	users: Object,
	filters: Object,
});

let search = ref(props.filters.search);

watch(
	search,
	debounce(function (value) {
		Inertia.get(
			'/brugere',
			{ search: value },
			{
				preserveState: true,
				replace: true,
			}
		);
	}, 300)
);

let modal = ref();

function openModal() {
    modal.value.openModal();
}
</script>

When I am trying to fire the openModal() function, then it opens the modal but closes again, almost immediately. I've been trying different things for the past 3-4 hours, and I can't seem to find whats wrong?

Thank you in advance!

0 likes
2 replies
phillipvarsted's avatar
phillipvarsted
OP
Best Answer
Level 4

Update!

Found out that I can't use Inertia's <Link> component when triggering the modal. Somehow it made an XHR call which resets the ref state.

Changed it to a and not it works :)

1 like

Please or to participate in this conversation.