Simple Popup Modal Made Easy

April 8, 2022

Written By: Matthew James

Simple Popup Modal Made Easy

Popup modals are used on just about every website out there. They are on every single site and we spend too much time creating them anyways. Well, great news! There is a brand new dialog tag we can utilize to simplify our jobs as developers. How much time I could have saved by using this the past three years, I would love to know. Let's check this dialog tag out.


Setting up the HTML

So let's set up the html for the simple dialog.


<dialog>
  <h2>This was easy.</h2>
</dialog>


This is pretty simple. You may be wondering, "Okay? Where is the modal at then?". That is a good question. By default the dialog is hidden. We could toggle this to show by adding a simple style of "display: block" or something along that path. However, you should avoid using css to show and hide the dialog tag as it will cause some issues.


Showing and closing the dialog with Javascript

The best practice for toggling these guys should be by these simple two little lines of code using pure Javascript.

document.querySelector('dialog').showModal()
document.querySelector('dialog').close()


We would utilize this line of code most commonly in a click event. However, you can call it where ever you want and need. Dialogs also have the ability to be closed with the "Escape" key by default as well!


Opening the dialog on page load

If you want your dialog to be opened by default, there is a simple way to do this. All we have to do is add an "open" attribute to the dialog tag.

<dialog open>
  <h2>This was easy.</h2>
</dialog>


There we go. We now know how to make a simple dialog. Let's now look at some simple tricks to animate the opening of the modal and utilizing my two favorite tools for front end development the popular Alpine.js framework and Tailwind.


Styling the backdrop

There is no difference to styling the dialog from any other html. However, there is a cool little pseudo item that the dialog has that will be simple to style. This pseudo item is called the backdrop. This is how we add a background color to outside of the dialog that covers the rest of the screen. This will most likely be an opaque color like a black that is a little see through. Let's take a look.


dialog::backdrop {
  background: rgb(0, 0, 0, 0.5)
}


That's all there really is to it. Let's style the rest of the dialog to make what we are working with a little more eye appealing.


Adding Some Tailwind

  <dialog class="rounded-lg p-8 relative">
    <svg xmlns="http://www.w3.org/2000/svg" class="absolute top-2 right-2 cursor-pointer" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
      <line x1="18" y1="6" x2="6" y2="18" />
      <line x1="6" y1="6" x2="18" y2="18" />
    </svg>
    <h1 class="font-extrabold text-orange-400 text-6xl">This was easy.</h1>
  </dialog>

Boom! Tailwind is one of my favorite tools to use and it is super easy and quick to whip something up like this. I also went ahead and added an "x" for an option to close the modal later too. Now that it looks just a tad better let's move on to the alpine.


Closing the dialog with Alpine

The main reason we want to utilize Alpine here is to make our life even easier. Let's add some more html and tailwind to add a button real quick to help with this example. We will also initialize this entire component as an Alpine component by adding "x-data='{}'" to the div that surrounds the component.


<div x-data='{}'>
  <div class="rounded-lg p-8 relative">
    <svg xmlns="http://www.w3.org/2000/svg" class="absolute top-2 right-2 cursor-pointer" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
      <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
      <line x1="18" y1="6" x2="6" y2="18" />
      <line x1="6" y1="6" x2="18" y2="18" />
    </svg>
    <h1 class="font-extrabold text-orange-400 text-6xl">This was easy.</h1>
  </dialog>

<button class="text-4xl bg-blue-400 text-white rounded-lg p-4 m-4">Open Model</button>
  </div>


Okay now this is looking a little more like a real life example. So, right now we are not doing anything with Alpine, it's just there. Let's add our toggle javascript code and combine with Some Alpine click functions.


<div x-data='{}'>
  <dialog class="rounded-lg p-0 p-8 relative">
  <svg x-on:click="closeDialog()" xmlns="http://www.w3.org/2000/svg" class="absolute top-2 right-2 cursor-pointer" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
    <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
    <line x1="18" y1="6" x2="6" y2="18" />
    <line x1="6" y1="6" x2="18" y2="18" />
  </svg>
  <h1 class="font-extrabold text-orange-400 text-6xl">This was easy.</h1>
  </dialog>

  <button x-on:click="openDialog()" class="text-4xl bg-blue-400 text-white rounded-lg p-4 m-4">Open Model</button>
</div>

<script>
  function openDialog() {
    document.querySelector('dialog').showModal()
  }

function closeDialog() {
    document.querySelector('dialog').close()
  }
</script>


As you can see in the above example, we have added some click events from Alpine that will close and open the dialog. On the "x" icon we call the function to close the dialog and on the button click we call the function to open or show the dialog. However, let's think about the user experience here. The user will probably expect to be able to close the dialog by clicking outside the dialog. Alpine makes this laughable easy. Let's look at how we can accomplish this.


<div x-data='{}'>
  <dialog class="rounded-lg p-0">
    <div x-on:click.outside="closeDialog()" class="p-8 relative">
  <svg x-on:click="closeDialog()" xmlns="http://www.w3.org/2000/svg" class="absolute top-2 right-2 cursor-pointer" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="#000000" fill="none" stroke-linecap="round" stroke-linejoin="round">
    <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
    <line x1="18" y1="6" x2="6" y2="18" />
    <line x1="6" y1="6" x2="18" y2="18" />
  </svg>
      <h1 class="font-extrabold text-orange-400 text-6xl">This was easy.</h1>
    </div>
  </dialog>

  <button x-on:click.stop="openDialog()" class="text-4xl bg-blue-400 text-white rounded-lg p-4 m-4">Open Model</button>
</div>

<script>
  function openDialog() {
    document.querySelector('dialog').showModal()
  }

function closeDialog() {
    document.querySelector('dialog').close()
  }
</script>


What did we do above? Well first we added a div to wrap the content in the dialog and added one of Alpine's coolest helper functions, "click.outside". Whenever a use clicks outside the div that has this function, whatever is in the quotes gets fired. So, why did we add a div inside the dialog and not just add the "click.outside" to the actual dialog tag? The dialog tag has the backdrop attached to it that extends the entire page. In order to get around this little annoying obstacle we add a div to wrap the content of the dialog and then have the "click.outside" helper function to that so that we can actually close the dialog. Also pay attention to the "p-0" class we added to the dialog. dialogs have a default padding added to them so we want to remove the default padding so that if the user clicks the padding it won't close the dialog. The last thing to note here is that we added a ".stop" to the buttons click event. This will prevent the "click.outside" event to fire from the dialog. Without it, the dialog will not open.


Adding an opening animation

The last thing we should do here is add an open animation to the dialog so it doesn't just appear and look unfinished. All we have to do here is just add an animation to the dialog when it has the "open" attribute. Let's see how this works.


dialog[open] {
    -webkit-animation: show 0.3s ease normal;
}
@-webkit-keyframes show{
    from {
        transform: scale(0)
    }
    to {
        transform: scale(1)
    }
}


This is pretty straight forward if you know animations. When the "open" attribute is added to the dialog this animation will fire.


Additional sources to learn and wrapping up

Thats the basics to the dialog. There is some more information to learn about the dialog tag that is a bit more in depth. If you are wanting to read more about it, check out the documentation here.


Check out the example we worked out above here