Display a modal using Vue and its Teleport component
Tags: vue, javascript, laravel, tutorial
Published: 2023-10-02 11:37:00

TL;DR: How to quickly implement a modal with Vue using its built-in Teleport component.

&nbsp;
&nbsp;

You will find a sample Laravel project on this [Github Repository](https://github.com/capsulescodes/articles).

&nbsp;
&nbsp;

While using a modal might seem obvious on a website, its implementation can sometimes be complex. To simplify this task, the Vue framework has introduced its built-in component `<Teleport>`. This component allows us to “teleport” a portion of a component's template into an existing DOM node outside of the component's DOM hierarchy.

&nbsp;
&nbsp;

Let's now determine the location of this external node by assigning the id `capsules` to `Welcome.vue`.

&nbsp;

`/resources/js/pages/Welcome.vue`

```vue
<script setup>

import logotype from '/public/assets/capsules-logotype-red-blue-home.svg';

</script>

<template>

    <div id="capsules" class="w-full min-h-screen flex flex-col font-sans text-primary-black">

        <div class="grow mx-8 lg:mx-auto max-w-screen-lg overflow-auto flex flex-col items-center justify-center text-center">

            <img class="w-24 h-24" v-bind:src="logotype">

            <h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />

        </div>

    </div>

</template>
```

&nbsp;
&nbsp;

This Vue component is, in fact, an InertiaJS page called when accessing the main route specified in the `web.php` file.

&nbsp;

`routes/web.php`


```php
<?php

use Illuminate\Support\Facades\Route;
use Inertia\Inertia;

Route::get( '/', fn() => Inertia::render( 'Welcome' ) );
```

&nbsp;
&nbsp;

![capsules-modal-001.png](https://capsules.codes/storage/canvas/images/kCL9kMzKnQbCvXhmodHpQ68fu4pHxwKIBaUCBuqq.png)

&nbsp;
&nbsp;

With the id `capsules` now assigned, the `Modal.vue` component will be able to use the built-in <Teleport> component on this element.

&nbsp;

`/resources/js/components/Modal.vue`

```vue
<script setup>

import { ref, onMounted } from 'vue';

const props = defineProps( { open : { type : Boolean, default : false } } );
const emits = defineEmits( [ 'toggle' ] );

const ready = ref( false );

function toggle()
{
    emits( 'toggle' );
}

onMounted( () => ready.value = true );

</script>

<template>

    <Teleport to="#capsules" v-if="ready">

        <Transition enter-active-class="duration-500 ease-in-out" enter-from-class="opacity-0" enter-to-class="opacity-100" leave-active-class="duration-500 ease-in" leave-from-class="opacity-100" leave-to-class="opacity-0">

            <div v-if="props.open" class="fixed w-full h-full flex items-center justify-center backdrop-blur-[1px] bg-primary-white bg-opacity-50" v-on:click="toggle()">

                <div class="relative m-16 p-2 rounded-xl flex flex-wrap items-center justify-center text-xs bg-white whitespace-pre shadow-2xl shadow-black/10" v-on:click.stop>

                    <slot />

                </div>

            </div>

        </Transition>

    </Teleport>

</template>
```

&nbsp;
&nbsp;

The `ready` variable allows the component to load so that it can trigger its Transition as smoothly as possible.

&nbsp;
&nbsp;

The `v-on:click.stop` event, on the other hand, prevents any potential click propagation to elements other than the modal itself.

&nbsp;
&nbsp;

The built-in `<Transition>` component can be used to apply entrance and exit animations to elements or components passed to it through its default slot. In this case, it involves a smooth opacity transition.

&nbsp;
&nbsp;

With the modal configuration completed, it is now time to create a component that will enable the display of this modal. In this case: a button.

&nbsp;

`resources/js/components/Button.vue`


```vue
<script setup>

import { ref, watch } from 'vue';

import Modal from '/resources/js/components/Modal.vue';

import logotype from '/public/assets/capsules-logotype-red-blue-home.svg';

const button = ref();
const isModalOpen = ref( false );

watch( () => isModalOpen.value, () => isModalOpen.value ? window.addEventListener( 'click', clickOutside ) : window.removeEventListener( 'click', clickOutside ) );

function clickOutside( event )
{
    if( event.target === button.value || !event.composedPath().includes( button.value ) ) isModalOpen.value = false;
}

</script>

<template>

    <div ref="button" class="m-4">

        <button class="px-4 py-2 text-sm rounded-md border border-primary-black hover:border-primary-red hover:text-primary-red transition-all" v-on:click="isModalOpen = true" v-bind:class="{ 'opacity-25' : isModalOpen }" v-bind:disabled="isModalOpen" v-text="'Open Modal'" />

        <Modal v-bind:open="isModalOpen" v-on:toggle="isModalOpen = false">

            <div class="p-8 flex flex-row space-x-4 rounded-lg">

                <img class="w-12 h-12 select-none" v-bind:src="logotype">

                <div class="font-mono flex items-center">

                    <h2 class="text-lg align-middle" v-text="'A wild MODAL appeared!'"/>

                </div>

            </div>

        </Modal>

    </div>

</template>
```

&nbsp;
&nbsp;

A variable `button` is created and associated with the parent div of the `Modal`, allowing us to monitor any clicks made outside of this component. A `watcher` is used to observe the `isModalOpen` variable, determining whether the `clickOutside` function should be triggered when clicking on the screen. If the `Modal` is open, the function is activated; otherwise, it is not.

&nbsp;
&nbsp;

The `Button.vue` component can be added to the `Welcome.vue` page.

&nbsp;

`resources/js/pages/Welcome.vue`

```vue
<script setup>

import logotype from '/public/assets/capsules-logotype-red-blue-home.svg';

import Button from '~/components/Button.vue';

</script>

<template>

    <div id="capsules" class="w-full min-h-screen flex flex-col font-sans text-primary-black">

        <div class="grow mx-8 lg:mx-auto max-w-screen-lg overflow-auto flex flex-col items-center justify-center text-center">

            <img class="w-24 h-24" v-bind:src="logotype">

            <h1 class="mt-4 text-6xl font-bold select-none header-mode" v-text="'Capsules Codes'" />

            <Button class="pt-8" />

        </div>

    </div>

</template>
```

&nbsp;
&nbsp;

![capsules-modal-002.png](https://capsules.codes/storage/canvas/images/DJJJjzzbSHdAhVLTe9ikZkSc9y99WfMpXsi9rFlR.png)

&nbsp;
&nbsp;

The `Modal` appears when clicking the button. It is now possible to customize the modal as desired: its position, dimensions, and actions. The `Reaction` component implemented on the [Capsules Codes Blog](https://capsules.codes) is also the result of this method.

&nbsp;
&nbsp;

![capsules-modal-003.png](https://capsules.codes/storage/canvas/images/R1YgIHbpyU4GMX4jk6Eqallazmp5hAB6i8t6LdRq.png)

&nbsp;
&nbsp;

Glad this helped.

&nbsp;
&nbsp;

Find out more on [Capsules](https://capsules.codes) or [X](https://x.com/capsulescodes)