J.Whiting.

Creating a Modal with Tailwind & AlpineJS

If you haven't tried out 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.

<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.

<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.

<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.
<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.

...

<!-- 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.

  <!-- 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>
You can find out more about why we use motion-safe variants by reading up on this awesome article from Smashing Magazine

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.

<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