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