Creating a Modal with Tailwind & AlpineJS
Tags: tailwindcss, aplinejs, html, modal
Published: 2021-03-26 00:55:56


If you haven't tried out [Alpine](https://github.com/alpinejs/alpine) before, it's a minimal JS framework with Vue like syntax that can be integrated into any website by just linking to the CDN.

Alpine combined with Tailwind allows for rapid development of websites - so it's understandable why I'm a fan.

In this article, we'll talk about how we can set up a modal that can be used anywhere on your site.

If you want, you can skip to the end for the full code.

## Setting up the Modal

Firstly, we need to set up somewhere to instantiate our Alpine data, for this example we'll use a `<div>` but it could be any element that you wish as long as it wraps the whole content.

```html
<div x-data="{ 'showModal': false }">

</div>
```

Inside of that, we need to add the basic structure of a modal. This will be the full-screen overlay background as well as the white box to contain the modal content itself.

We're also adding `x-show` to the modal so that this only shows when `showModal` is set to true in the Alpine wrapper.

```html
<div x-data="{ 'showModal': false }">
  <!-- Modal -->
  <div
    class="fixed inset-0 z-30 flex items-center justify-center overflow-auto bg-black bg-opacity-50"
    x-show="showModal"
  >
    <!-- Modal inner -->
    <div class="max-w-3xl px-6 py-4 mx-auto text-left bg-white rounded shadow-lg">

    </div>
  </div>
</div>
```

Next, let's add a trigger so that we can open the modal. In this example, it's a button but it could also be any `a` tag as well.

```html
<div x-data="{ 'showModal': false }">

  <!-- Trigger for Modal -->
  <button type="button" @click="showModal = true">Open Modal</button>

  <!-- Modal -->
  <div
    class="fixed inset-0 z-30 flex items-center justify-center overflow-auto bg-black bg-opacity-50"
    x-show="showModal"
  >

    ...

  </div>
</div>
```

When we press the button, we will see the modal popup but we don't have any way to close it. Let's deal with that.

  - add `@keydown.escape` to the wrapper of our modal.
  - add a `@click.away` event listener to the inner modal so that if we click outside of the white content it will also close.

```html
<div
  x-data="{ 'showModal': false }"
  @keydown.escape="showModal = false"
>
  ...

  <!-- Modal -->
  <div
    class="fixed inset-0 z-30 flex items-center justify-center overflow-auto bg-black bg-opacity-50"
    x-show="showModal"
  >
    <!-- inner modal -->
    <div
        class="max-w-3xl px-6 py-4 mx-auto text-left bg-white rounded shadow-lg"
        @click.away="showModal = false"
    >

    </div>
  </div>
</div>
```

Next, we will set up some content for the inside. Inside of the modal, we will add a title and a close button to give the user an intuitive way to also get out of the modal.

```html
...

<!-- inner modal -->
<div
  class="max-w-3xl px-6 py-4 mx-auto text-left bg-white rounded shadow-lg"
  @click.away="showModal = false"

>
  <!-- Title / Close-->
  <div class="flex items-center justify-between">
      <h5 class="mr-3 text-black max-w-none">Title</h5>

      <button type="button" class="z-50 cursor-pointer" @click="showModal = false">
          <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
      </svg>
      </button>
  </div>

  <!-- content -->
  <div>Content goes here</div>
</div>

...
```

Finally, let's add some animation to the opening using Alpine's `transition` methods. On the modal itself, we'll add some Tailwind classes that smooth in the entrance of the modal. We'll also add the `motion-safe` property to ensure that we adhere to the user's preferences.

```html
  <!-- Modal -->
  <div
      class="max-w-3xl px-6 py-4 mx-auto text-left bg-white rounded shadow-lg"
      @click.away="showModal = false"
      x-transition:enter="motion-safe:ease-out duration-300"
      x-transition:enter-start="opacity-0 scale-90"
      x-transition:enter-end="opacity-100 scale-100"
  >

    ...

  </div>
```

<blockquote class="info">You can find out more about why we use <code>motion-safe</code> variants by reading up on this awesome article from <a href="https://www.smashingmagazine.com/2020/09/design-reduced-motion-sensitivities/" target="_blank" rel="nofollow noreferrer">Smashing Magazine</a></blockquote>

Perfect, we have a modal built with Tailwind + Alpine JS.

## Finished Code

Here is the finished code that you can copy & paste into your website.

```html
<div
  x-data="{ 'showModal': false }"
  @keydown.escape="showModal = false"
>
    <!-- Trigger for Modal -->
    <button type="button" @click="showModal = true">Open Modal</button>

    <!-- Modal -->
    <div
        class="fixed inset-0 z-30 flex items-center justify-center overflow-auto bg-black bg-opacity-50"
        x-show="showModal"
    >
        <!-- Modal inner -->
        <div
            class="max-w-3xl px-6 py-4 mx-auto text-left bg-white rounded shadow-lg"
            @click.away="showModal = false"
            x-transition:enter="motion-safe:ease-out duration-300"
            x-transition:enter-start="opacity-0 scale-90"
            x-transition:enter-end="opacity-100 scale-100"
        >
            <!-- Title / Close-->
            <div class="flex items-center justify-between">
                <h5 class="mr-3 text-black max-w-none">Title</h5>

                <button type="button" class="z-50 cursor-pointer" @click="showModal = false">
                    <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor">
                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
                    </svg>
                </button>
            </div>

            <!-- content -->
            <div>Content goes here</div>
        </div>
    </div>
</div>
```

If there are any errors, please reach out to me on Twitter [@jackabox](https://twitter.com/jackabox)