Modal
Dialog overlay that requires user attention or interaction.
TailwindCSS
Alpine.js
Are you absolutely sure?
This action cannot be undone. This will permanently delete your account and remove your data from our servers.
package showcase
import "github.com/axzilla/templui/pkg/components"
templ Modal() {
@components.ModalTrigger("default-modal") {
@components.Button(components.ButtonProps{Text: "Open Modal"})
}
@components.Modal(components.ModalProps{ID: "default-modal", Class: "max-w-md"}) {
@components.ModalHeader() {
Are you absolutely sure?
}
@components.ModalBody() {
This action cannot be undone. This will permanently delete your account and remove your data from our servers.
}
@components.ModalFooter() {
<div class="flex gap-2">
@components.ModalClose("default-modal") {
@components.Button(components.ButtonProps{
Text: "Cancel",
})
}
@components.ModalClose("default-modal") {
@components.Button(components.ButtonProps{
Text: "Continue",
Variant: components.ButtonVariantSecondary,
})
}
</div>
}
}
}
package components
import "github.com/axzilla/templui/pkg/utils"
type ModalProps struct {
ID string // Unique identifier for control
Class string // Additional CSS classes
}
// Dialog overlay that requires user attention or interaction.
templ Modal(props ModalProps) {
<div
x-data="modal"
x-on:open-modal.window="handleOpenModal"
x-on:close-modal.window="handleCloseModal"
data-modal-id={ props.ID }
x-show="open"
x-transition:enter="transition ease-out duration-300"
x-transition:enter-start="opacity-0"
x-transition:enter-end="opacity-100"
x-transition:leave="transition ease-in duration-200"
x-transition:leave-start="opacity-100"
x-transition:leave-end="opacity-0"
class="fixed inset-0 z-50 flex items-center justify-center overflow-y-auto"
aria-labelledby="modal-title"
role="dialog"
aria-modal="true"
>
<div class="fixed inset-0 bg-black bg-opacity-50 transition-opacity" aria-hidden="true"></div>
<div
class={
utils.TwMerge(
"relative bg-background rounded-lg border text-left overflow-hidden shadow-xl transform transition-all sm:my-8 w-full",
props.Class),
}
@click.away="handleClickAway"
>
{ children... }
</div>
</div>
}
// ModalTrigger creates clickable elements that open a modal
templ ModalTrigger(id string) {
<span
x-data="modalTriggers"
data-modal-id={ id }
@click="openModal"
>
{ children... }
</span>
}
// ModalClose creates clickable elements that close a modal
templ ModalClose(id string) {
<span
x-data="modalTriggers"
data-modal-id={ id }
@click="closeModal"
>
{ children... }
</span>
}
// ModalHeader renders the modal title section
templ ModalHeader() {
<div class="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
<h3 class="text-lg leading-6 font-medium text-foreground" id="modal-title">
{ children... }
</h3>
</div>
}
/// ModalBody renders the main modal content area
templ ModalBody() {
<div class="px-4 pt-5 pb-4 sm:p-6 sm:pb-4">
{ children... }
</div>
}
// ModalFooter renders the modal action buttons section
templ ModalFooter() {
<div class="px-4 py-3 sm:px-6 sm:flex sm:flex-row-reverse">
{ children... }
</div>
}
templ ModalScript() {
{{ handler := templ.NewOnceHandle() }}
@handler.Once() {
<script defer nonce={ templ.GetNonce(ctx) }>
document.addEventListener('alpine:init', () => {
Alpine.data('modal', () => ({
open: false,
handleOpenModal(event) {
if (event.detail.id === this.$el.dataset.modalId) {
this.open = true
}
},
handleCloseModal(event) {
if (event.detail.id === this.$el.dataset.modalId) {
this.open = false
}
},
handleClickAway() {
this.open = false;
},
}))
Alpine.data('modalTriggers', () => ({
openModal() {
this.$dispatch('open-modal', {
id: this.$el.dataset.modalId
})
},
closeModal() {
this.$dispatch('close-modal', {
id: this.$el.dataset.modalId
})
}
}))
})
</script>
}
}
Usage
1. Add the script to your page/layout:
// Option A: All components (recommended)
@utils.ComponentScripts()
// Option B: Just Modal
@components.ModalScript()
2. Use the component:
@components.Modal(components.ModalProps{...})