Simple, reusable Svelte Modal
Tags: svelte, webdev, beginners
Published: 2022-08-23 20:35:53

## Intro
So I noticed [this](https://dev.to/nicvazquez/how-to-create-a-pop-up-modal-in-react-j3f) post about make a React Modal so i thought I would post my take on a Svelte Modal but with more features and more reusable.

So this for this Modal you can display it and change text and title by just setting the values of a store.

You will also be able to close the modal by clicking outside the modal-box, pressing Escape key or pressing the OK-button.

This is a small variation of a component I created for my submission to the redis hackathon where it was used to display error messages.

## Step by step
Start with open a terminal/console

Go to the directory you want the project

Run
```bash
npm create vite@latest
```
Type a project name, in my case svelte-modal
Choose svelte as framework
Choose regular svelte
![cmd](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/bq5du0a8x3ydpfwt90ry.PNG)

As instruction says, got to directory and run
```bash
npm install
```
I am lazy and don't like writing a lot of CSS so for this I will use [picocss](https://picocss.com/)
So now run
```bash
npm install @picocss/pico
```
Start up the dev server with
```bash
npm run dev
```
You should now see this in the browser.
![starterpage](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ivsvdnzk0altcy891hf1.PNG)

Let's get to code!
Go to the App.svelte file in the src folder. It contains the boilerplate for the page you just open in the browser.
Let's delete it all and replace with:
```js
<script>
</script>

<main>
	<button>Show modal</button>
</main>
```
Let's go to `app.css` and just delete all the contents as it's not needed.

Now, lets import pico.css
goto `main.js`and add this
```js
import '@picocss/pico/css/pico.min.css';
```
in the top of the file.
Should now look like this:
```js
import '@picocss/pico/css/pico.min.css';
import './app.css';
import App from './App.svelte';

const app = new App({
	target: document.getElementById('app'),
});

export default app;
```
This modal will make use of a Svelte store. This store will hold the modals message, title and open/close state. By using a store means we will be able to trigger the showing of the modal from both Svelte components and regular Javascript code.

Let's create a new folder named `stores` inside the `src/lib` folder. Create a new file in the `stores`folder named `index.js`

Put below code in there. If you want can set a default title and message to anything you want.

```js
import { writable } from 'svelte/store';
export const modal = writable({ open: false, title: 'Default title', message: 'Default message' });
```
Store is now done and ready to be used.
Let's create the actual modal component. I usually create a components folder but for this example we will just put the file in the 'src/lib' folder.

Create a file named `Modal.svelte`

We put below code in the file, I will explain what it does a bit later.
```js
<script>
	import { modal } from '../lib/stores/';
	const close = () => ($modal.open = false);

	const handle_keydown = (e) => {
		if (e.key === 'Escape') return close();
	};
</script>

<svelte:window on:keydown|once={handle_keydown} />

<dialog open on:click|self|preventDefault={close}>
	<article>
		<h3>{$modal.title}</h3>
		<p>
			{$modal.message}
		</p>
		<footer>
			<button on:click|once={close}>OK</button>
		</footer>
	</article>
</dialog>

```
Let's go back to `App.svelte` and wire things up so can try things out.
```js
<script>
	import Modal from './lib/Modal.svelte';
	import { modal } from './lib/stores/';
</script>

<main>
	{#if $modal.open == true}
		<Modal />
	{/if}
	<button on:click={() => ($modal.open = true)}>Show modal</button>
</main>

```
We now got this:

![modal_opener](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/q4qu8l5pjdj6v2vnb02d.PNG)
And if pressing the button:

![modal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/17fnf5d2gwcl5v5z0w61.PNG)

So we import the Modal component and the modal store
We put the Modal component inside a Svelte-if. $modal references the store, so we only show the component if `$modal.open` is true.

We now have a modal that will be closed if click outside the modal, if press escape button on keyboard or if pressing the modal ok-button. It can be displayed from anywhere just importing the store and set open to true and in the same way you can set the title and message to display different things, for example error messages.

Now let's dig into the code that makes it work. Seeing to the functionality it provides there is a surprisingly small amount of code.

Here is the `Modal.svelte` component again
```js
<script>
	import { modal } from '../lib/stores/';
	const close = () => ($modal.open = false);

	const handle_keydown = (e) => {
		if (e.key === 'Escape') return close();
	};
</script>

<svelte:window on:keydown|once={handle_keydown} />

<dialog open on:click|self|preventDefault|once={close}>
	<article>
		<h3>{$modal.title}</h3>
		<p>
			{$modal.message}
		</p>
		<footer>
			<button on:click|once={close}>OK</button>
		</footer>
	</article>
</dialog>
```
Import the store so can use it.
```js
import { modal } from '../lib/stores/';
```

Function that will close the modal by setting the store.
```js
const close = () => ($modal.open = false);
```
Handler for keydown that will be shown a bit further down, only call the close() function when escape is pressed.
```js
const handle_keydown = (e) => {
		if (e.key === 'Escape') return close();
	};
```
In Svelte we can use the special <svelte:window> tag here we use it to bind an eventlistener for keydown to the window object, same as you would be able to do in regular javascript.
It uses the special `once` DOM modifier. By using `once` the listener will delete itself after running once, it can be useful in many situations
```js
<svelte:window on:keydown|once={handle_keydown} />
```
In pico.css modals are easily created with <dialog> element with an <article> element inside. These are not special, just regular html-elemnts.

But let's analyze the below code. There is a `on:click` listener bound to the `<dialog>`element, so the semi-transparent area outside the modal-box. And it runs the close() function making the dialog close.

```js
<dialog open on:click|self|preventDefault|once={close}>
```

By using `self` the on:click will only fire if we explicit click anywhere on the `<dialog>` element, i.e. the background. Without this the on:click would also fire if clicked anywhere on the modal-box. Handy feature :) 

`preventDefault` should not be needed but I put it there cause i wanted to show it. It prevents default behavior and is the same as using the preventDefault() in an eventhandler, this can be handy if you for example want to define an inline function for the on:click handler, like:
```js
on:submit|preventDefault={(e)=>do something simple}
```
Svelte is filled with smart stuff like this, check the documentation if your curios about other DOM modifiers.


Hope you learned something. This was tiring to write, I will stick to writing devlogs. ;)