Example

<button
  x-data
  @click="$dispatch('open-modal', { id: 'modal' })"
  class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
>
  Open modal
</button>

<div x-data="modal" id="modal" x-cloak>
  <div
    x-bind="backdrop"
    x-alt-transition='{
      "enter": ["opacity-0", "transition ease-out duration-200", "opacity-100"],
      "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0"]
    }'
    class="fixed inset-0 z-40 overflow-y-auto bg-black/50"
  ></div>
  <div
    x-bind="container"
    @click="closeNotStatic()"
    class="fixed inset-0 z-40 overflow-y-auto"
  >
    <div
      x-bind="positioner"
      class="relative mx-auto flex min-h-full items-center px-6 py-6 md:w-6/12 md:px-0"
    >
      <div
        x-bind="content"
        x-alt-transition='{
          "enter": ["opacity-0 scale-[0.97]", "transition ease-out duration-200 delay-100", "opacity-100"],
          "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0 scale-[0.97]"]
        }'
        x-trap="isOpen"
        class="flex-1 overflow-auto rounded-md bg-white text-text-800 shadow-lg dark:bg-dark-800 dark:text-text-300"
      >
        <header
          class="flex items-center justify-between px-6 py-6 text-lg font-medium leading-6 text-text-800 dark:text-text-300"
        >
          Lorem Ipsum is simply dummy text
          <template x-if="closable">
            <button @click="close()">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="16"
                height="16"
                fill="currentColor"
                viewBox="0 0 16 16"
              >
                <path
                  d="M1.293 1.293a1 1 0 0 1 1.414 0L8 6.586l5.293-5.293a1 1 0 1 1 1.414 1.414L9.414 8l5.293 5.293a1 1 0 0 1-1.414 1.414L8 9.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L6.586 8 1.293 2.707a1 1 0 0 1 0-1.414z"
                />
              </svg>
            </button>
          </template>
        </header>
        <main class="px-6 py-5">
          Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's
          standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to
          make a type specimen book. It has survived not only five centuries, but also the leap into electronic
          typesetting, remaining essentially unchanged.
        </main>
        <footer class="flex justify-end gap-x-6 px-10 py-6">
          <button @click="close()" class="font-semibold">OK</button>
          <button @click="close()" class="font-semibold">Close</button>
        </footer>
      </div>
    </div>
  </div>
</div>

Usage

<script defer src="https://cdn.jsdelivr.net/npm/litewind-alpine@0.x.x/components/modal/dist/cdn.min.js"></script>

The data for the component is provided by the modal function in the x-data directive and the props in the data-* attributes.

Props

data-static
false Boolean

Static modal cannot be closed by clicking outside of it.


data-closable
true Boolean

Set to false to remove close button.

Trap focus

The modal uses focus plugin (x-trap directive) to trap focus inside modal window. While this plugin is optional, it is recommended for better accessibility and better overall usability. Add it with the following script tag:

<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/focus@3.x.x/dist/cdn.min.js"></script>

Check the offical plugin documentation for additional install options and features.

Opening modal

To show a modal, simply dispatch open-modal event anywhere in your application. The event data should be either a string representing the ID of the modal, or an object with the following properties:

  • id - the id of the modal,
  • options - additional custom options that can be referenced in the template. This is useful, for example, for creating reusable modal dialogs with customizable content, buttons etc.

By default, the modal is centered and 50% wide. You can change its placement and size using Tailwind classes on the positioner element. Below is an example showing several possible placement and size options:

Example
Lorem Ipsum is simply dummy text
Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged.

<div class="flex flex-col gap-y-4">
  <button
    x-data
    @click="$dispatch('open-modal', { id: 'modalPosition', options: { position: 'mx-auto items-center px-6 py-6 md:w-6/12 md:px-0' } })"
    class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
  >
    Open default modal
  </button>
  <button
    x-data
    @click="$dispatch('open-modal', { id: 'modalPosition', options: { position: 'mx-auto items-start px-6 py-6 md:w-6/12 md:px-0' } })"
    class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
  >
    Open top modal
  </button>
  <button
    x-data
    @click="$dispatch('open-modal', { id: 'modalPosition', options: { position: 'mx-auto items-center px-6 py-6 md:w-4/12 md:px-0' } })"
    class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
  >
    Open small modal
  </button>
  <button
    x-data
    @click="$dispatch('open-modal', { id: 'modalPosition', options: { position: '' } })"
    class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
  >
    Open fullscreen modal
  </button>
</div>

<div x-data="modal" id="modalPosition" x-cloak>
  <div
    x-bind="backdrop"
    x-alt-transition='{
      "enter": ["opacity-0", "transition ease-out duration-200", "opacity-100"],
      "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0"]
    }'
    class="fixed inset-0 z-40 overflow-y-auto bg-black/50"
  ></div>
  <div
    x-bind="container"
    @click="closeNotStatic()"
    class="fixed inset-0 z-40 overflow-y-auto"
  >
    <div
      x-bind="positioner"
      class="relative flex min-h-full"
      :class="options.position"
    >
      <div
        x-bind="content"
        x-alt-transition='{
          "enter": ["opacity-0 scale-[0.97]", "transition ease-out duration-200 delay-100", "opacity-100"],
          "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0 scale-[0.97]"]
        }'
        x-trap="isOpen"
        class="flex-1 overflow-auto rounded-md bg-white text-text-800 shadow-lg dark:bg-dark-800 dark:text-text-300"
      >
        <header
          class="flex items-center justify-between px-6 py-6 text-lg font-medium leading-6 text-text-800 dark:text-text-300"
        >
          Lorem Ipsum is simply dummy text
          <template x-if="closable">
            <button @click="close()">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="16"
                height="16"
                fill="currentColor"
                viewBox="0 0 16 16"
              >
                <path
                  d="M1.293 1.293a1 1 0 0 1 1.414 0L8 6.586l5.293-5.293a1 1 0 1 1 1.414 1.414L9.414 8l5.293 5.293a1 1 0 0 1-1.414 1.414L8 9.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L6.586 8 1.293 2.707a1 1 0 0 1 0-1.414z"
                />
              </svg>
            </button>
          </template>
        </header>
        <main class="px-6 py-5">
          Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's
          standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to
          make a type specimen book. It has survived not only five centuries, but also the leap into electronic
          typesetting, remaining essentially unchanged.
        </main>
        <footer class="flex justify-end gap-x-6 px-10 py-6">
          <button @click="close()" class="font-semibold">OK</button>
          <button @click="close()" class="font-semibold">Close</button>
        </footer>
      </div>
    </div>
  </div>
</div>

Modals are often used for dialogs. Below is an example of a reusable dialog component. The event data is used to set the dialog's content and define callbacks for the user's actions. In the second example, dispatching the event inside a promise allows you to pause code execution and await for user confirmation.

Example

<div class="flex flex-col gap-y-4">
  <button
    x-data
    @click="$dispatch('open-modal', { id: 'modalDialog', options: { header: 'Delete file', text: 'Delete file?', variant: 'danger', resolve: (res) => console.log(res) } })"
    class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
  >
    Open modal dialog
  </button>
  <button
    x-data
    @click="
      async () => {
        let file = 'file'
        let confirm = new Promise((resolve, reject) => {
          $dispatch('open-modal', { id: 'modalDialog', options: { header: 'Delete file', text: `Delete ${file}?`, variant: 'danger', resolve, reject } })
        })
        let res = await confirm
        console.log(res)
      }
    "
    class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
  >
    Open promise modal dialog
  </button>
</div>

<div x-data="modal" id="modalDialog" x-cloak>
  <div
    x-bind="backdrop"
    x-alt-transition='{
      "enter": ["opacity-0", "transition ease-out duration-200", "opacity-100"],
      "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0"]
    }'
    class="fixed inset-0 z-40 overflow-y-auto bg-black/50"
  ></div>
  <div
    x-bind="container"
    @click="options.resolve('cancel');closeNotStatic()"
    class="fixed inset-0 z-40 overflow-y-auto"
  >
    <div
      x-bind="positioner"
      class="relative mx-auto flex min-h-full items-center px-6 py-6 md:w-4/12 md:px-0"
    >
      <div
        x-bind="content"
        x-alt-transition='{
          "enter": ["opacity-0 scale-[0.97]", "transition ease-out duration-200 delay-100", "opacity-100"],
          "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0 scale-[0.97]"]
        }'
        x-trap="isOpen"
        class="flex-1 overflow-auto rounded-md bg-white text-text-800 shadow-lg dark:bg-dark-800 dark:text-text-300"
      >
        <header
          class="flex items-center justify-between px-6 py-6 text-lg font-medium leading-6 text-text-800 dark:text-text-300"
        >
          <div class="flex items-center gap-x-4">
            <svg
              xmlns="http://www.w3.org/2000/svg"
              class="h-6 w-6"
              fill="currentColor"
              viewBox="0 0 16 16"
            >
              <path d="M8 15A7 7 0 1 1 8 1a7 7 0 0 1 0 14m0 1A8 8 0 1 0 8 0a8 8 0 0 0 0 16"/>
              <path d="M7.002 11a1 1 0 1 1 2 0 1 1 0 0 1-2 0M7.1 4.995a.905.905 0 1 1 1.8 0l-.35 3.507a.552.552 0 0 1-1.1 0z"/>
            </svg>
            <span x-text="options.header"></span>
          </div>
          <template x-if="closable">
            <button @click="options.resolve('cancel');close()">
              <svg
                xmlns="http://www.w3.org/2000/svg"
                width="16"
                height="16"
                fill="currentColor"
                viewBox="0 0 16 16"
              >
                <path
                  d="M1.293 1.293a1 1 0 0 1 1.414 0L8 6.586l5.293-5.293a1 1 0 1 1 1.414 1.414L9.414 8l5.293 5.293a1 1 0 0 1-1.414 1.414L8 9.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L6.586 8 1.293 2.707a1 1 0 0 1 0-1.414z"
                />
              </svg>
            </button>
          </template>
        </header>
        <main class="px-6 py-2">
          <span x-text="options.text"></span>
        </main>
        <footer class="flex justify-end gap-x-6 px-10 py-6">
          <button
            @click="options.resolve('ok');close()"
            class="font-semibold"
            :class="{ 'text-danger-500 dark:text-danger-600 font-bold!': options.variant === 'danger' }"
          >
            OK
          </button>
          <button @click="options.resolve('cancel');close()" class="font-semibold">Close</button>
        </footer>
      </div>
    </div>
  </div>
</div>

Modals can also be used to make image viewers. Here is an example.

Example

<button
  x-data
  @click="$dispatch('open-modal', { id: 'modalImage' })"
  class="mx-auto block rounded-md border-violet-700 bg-violet-500 px-4 py-2 font-medium text-gray-100 hover:bg-violet-600 focus:ring-violet-200 dark:bg-violet-500 dark:hover:bg-violet-600"
>
  Open modal image viewer
</button>

<div
  x-data="{images: ['mononoke033.jpg', 'mononoke035.jpg', 'IMG_2648.jpg', 'IMG_2657.jpg'], currentImage: 0}"
>
  <div x-data="modal" id="modalImage" x-cloak>
    <div
      x-bind="backdrop"
      x-alt-transition='{
        "enter": ["opacity-0", "transition ease-out duration-200", "opacity-100"],
        "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0"]
      }'
      class="fixed inset-0 z-40 overflow-y-auto bg-black/70 backdrop-blur-xs dark:bg-black/70"
    ></div>
    <div
      x-bind="container"
      @click="closeNotStatic()"
      class="fixed inset-0 z-40 overflow-y-auto"
    >
      <div
        v-bind="positioner"
        class="relative mx-auto flex min-h-full w-max items-center md:px-0"
      >
        <div
          x-bind="content"
          x-alt-transition='{
            "enter": ["opacity-0 scale-[0.97]", "transition ease-out duration-200 delay-100", "opacity-100"],
            "leave": ["opacity-100", "transition ease-in duration-200", "opacity-0 scale-[0.97]"]
          }'
          x-trap="isOpen"
          class="flex-1 overflow-auto rounded-md bg-white text-text-800 shadow-lg dark:bg-dark-800 dark:text-text-300"
        >
          <main class="group relative">
            <img
              :src="'/images/' + images[currentImage]"
              alt=""
              class="max-h-[92vh] max-w-[90vw]"
            >
            <button
              @click="close()"
              class="absolute right-6 top-6 h-6 w-6 text-white/80"
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="currentColor"
                viewBox="0 0 16 16"
              >
                <path
                  d="M1.293 1.293a1 1 0 0 1 1.414 0L8 6.586l5.293-5.293a1 1 0 1 1 1.414 1.414L9.414 8l5.293 5.293a1 1 0 0 1-1.414 1.414L8 9.414l-5.293 5.293a1 1 0 0 1-1.414-1.414L6.586 8 1.293 2.707a1 1 0 0 1 0-1.414z"
                />
              </svg>
            </button>
            <div
              class="absolute bottom-0 flex w-full justify-center-safe gap-x-4 overflow-x-auto p-2 max-w-full"
            >
              <template x-for="(image, index) in images">
                <img
                  @click="currentImage = index"
                  :src="'/images/' + image"
                  alt=""
                  class="h-20 cursor-pointer rounded-sm opacity-0 shadow-md shadow-black/30 transition-opacity duration-300 hover:scale-105 group-hover:opacity-100"
                  :class="{'ring-4 ring-indigo-500' : image === images[currentImage]}"
                >
              </template>
            </div>
            <button
              @click="currentImage = currentImage - 1 < 0 ? images.length - 1 : currentImage - 1"
              class="absolute left-5 top-1/2 -translate-y-1/2 text-white opacity-0 transition-opacity duration-300 group-hover:opacity-80"
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="currentColor"
                viewBox="0 0 320 512"
                class="h-12 w-12"
              >
                <path
                  d="M9.4 233.4c-12.5 12.5-12.5 32.8 0 45.3l192 192c12.5 12.5 32.8 12.5 45.3 0s12.5-32.8 0-45.3L77.3 256 246.6 86.6c12.5-12.5 12.5-32.8 0-45.3s-32.8-12.5-45.3 0l-192 192z"
                />
              </svg>
            </button>
            <button
              @click="currentImage = currentImage + 1 > images.length - 1 ? 0 : currentImage + 1"
              class="absolute right-5 top-1/2 -translate-y-1/2 text-white opacity-0 transition-opacity duration-300 group-hover:opacity-80"
            >
              <svg
                xmlns="http://www.w3.org/2000/svg"
                fill="currentColor"
                viewBox="0 0 320 512"
                class="h-12 w-12"
              >
                <path
                  d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"
                />
              </svg>
            </button>
          </main>
        </div>
      </div>
    </div>
  </div>
</div>
Litewind-alpine 0.1.0