SuperModal
v1.0.0 · Last updated April 2026
Accessible, animated modals with HTMX-powered dynamic content loading. Complete documentation of all configuration options and patterns.
# Installation
SuperModal is included in the AZ UI Kit bundle.
<link rel="stylesheet" href="https://cdn.tunet.dev/az-ui-kit/1.0.0/az-ui-kit.min.css">
<script src="https://cdn.tunet.dev/az-ui-kit/1.0.0/az-ui-kit.min.js"></script>
# Basic Usage
Create a modal with a trigger button and modal content.
<!-- Trigger Button -->
<button data-az-modal-trigger="my-modal">
Open Modal
</button>
<!-- Modal -->
<div data-az-component="super-modal"
data-az-modal-id="my-modal">
<!-- Backdrop -->
<div data-az-modal-backdrop></div>
<!-- Dialog -->
<div data-az-modal-dialog>
<!-- Header -->
<div data-az-modal-header>
<h2>Modal Title</h2>
<button data-az-modal-close>×</button>
</div>
<!-- Body -->
<div data-az-modal-body>
<p>Modal content goes here.</p>
</div>
<!-- Footer -->
<div data-az-modal-footer>
<button data-az-modal-close>Cancel</button>
<button data-az-modal-action="confirm">Confirm</button>
</div>
</div>
</div>
# Data Attributes Reference
Modal Container Attributes
| Attribute | Type | Default | Description |
|---|---|---|---|
| data-az-component | string | — | Must be "super-modal" |
| data-az-modal-id | string | required | Unique identifier for the modal |
| data-az-modal-size | string | "md" | "sm" | "md" | "lg" | "xl" | "full" |
| data-az-modal-animation | string | "fade" | "fade" | "slide" | "scale" | "none" |
| data-az-modal-position | string | "center" | "center" | "top" | "right" | "bottom" | "left" |
| data-az-modal-closable | boolean | true | Allow closing via backdrop/escape |
| data-az-modal-persistent | boolean | false | Keep content in DOM when closed |
| data-az-modal-stack | boolean | true | Allow nested/stacked modals |
| data-az-modal-scroll | string | "body" | "body" | "dialog" — scrollable area |
| data-az-modal-focus-trap | boolean | true | Trap keyboard focus within modal |
| data-az-modal-auto-focus | string | "first" | Selector or "first" | "none" |
Trigger Attributes
| Attribute | Description |
|---|---|
| data-az-modal-trigger | Modal ID to open |
| data-az-modal-close | Close the current modal (no value needed) |
| data-az-modal-action | Action name to emit on click ("confirm", "delete", etc.) |
Part Attributes (semantic regions)
| Attribute | Description |
|---|---|
| data-az-modal-backdrop | Background overlay element |
| data-az-modal-dialog | Main dialog container |
| data-az-modal-header | Header region (title, close button) |
| data-az-modal-body | Main content area |
| data-az-modal-footer | Footer region (actions) |
# Sizes
Pre-defined modal sizes for common use cases.
| Size | Max Width | Use Case |
|---|---|---|
| sm | 400px | Confirmations, alerts, simple inputs |
| md | 560px | Forms, content previews (default) |
| lg | 800px | Complex forms, tables, wizards |
| xl | 1140px | Dashboards, reports, media viewers |
| full | 100vw - 2rem | Full-screen experiences |
<div data-az-component="super-modal"
data-az-modal-id="large-modal"
data-az-modal-size="lg">
...
</div>
# Animations
Built-in animation presets with customizable duration and easing.
fade
Simple opacity transition (default)
data-az-modal-animation="fade"
slide
Slides in from the position direction
data-az-modal-animation="slide"
scale
Scales up from center with fade
data-az-modal-animation="scale"
none
Instant show/hide without animation
data-az-modal-animation="none"
Custom Animation Timing
<div data-az-component="super-modal"
data-az-modal-id="custom-animation"
data-az-modal-animation="scale"
data-az-modal-duration="300"
data-az-modal-easing="cubic-bezier(0.16, 1, 0.3, 1)">
...
</div>
# HTMX Integration
Load modal content dynamically from the server.
Load Content on Open
<!-- Trigger with dynamic content -->
<button data-az-modal-trigger="user-modal"
hx-get="/users/123/profile"
hx-target="#user-modal [data-az-modal-body]"
hx-trigger="click">
View Profile
</button>
<!-- Modal with placeholder -->
<div data-az-component="super-modal"
data-az-modal-id="user-modal"
data-az-modal-loading="true">
<div data-az-modal-backdrop></div>
<div data-az-modal-dialog>
<div data-az-modal-header>
<h2>User Profile</h2>
<button data-az-modal-close>×</button>
</div>
<div data-az-modal-body>
<!-- Loading state shown while fetching -->
<div data-az-modal-loader>
Loading...
</div>
</div>
</div>
</div>
Submit Forms Inside Modal
<div data-az-modal-body>
<form hx-post="/users/create"
hx-swap="none"
data-az-modal-form>
<input name="name" placeholder="Name">
<input name="email" placeholder="Email">
<button type="submit">Create User</button>
</form>
</div>
<script>
// Close modal on successful form submission
document.addEventListener('htmx:afterRequest', (e) => {
if (e.detail.successful && e.target.hasAttribute('data-az-modal-form')) {
SuperModal.close();
}
});
</script>
Dynamic Modal Content Response
def profile_modal(request, user_id):
user = get_object_or_404(User, pk=user_id)
# Return partial HTML
return render(request, 'partials/user_profile.html', {'user': user})
# Nested Modals
Stack modals on top of each other with proper z-index and focus management.
<!-- Parent Modal -->
<div data-az-component="super-modal"
data-az-modal-id="parent-modal"
data-az-modal-stack="true">
<div data-az-modal-dialog>
<div data-az-modal-body>
<p>This is the parent modal.</p>
<!-- Trigger for nested modal -->
<button data-az-modal-trigger="confirm-modal">
Open Confirmation
</button>
</div>
</div>
</div>
<!-- Nested Modal (Confirmation) -->
<div data-az-component="super-modal"
data-az-modal-id="confirm-modal"
data-az-modal-size="sm">
<div data-az-modal-dialog>
<div data-az-modal-body>
<p>Are you sure?</p>
</div>
<div data-az-modal-footer>
<button data-az-modal-close>Cancel</button>
<button data-az-modal-action="confirm">Confirm</button>
</div>
</div>
</div>
Note: When closing nested modals, focus automatically returns to the parent modal. Pressing Escape closes only the topmost modal.
# Events
| Event | Description | Detail |
|---|---|---|
| az:modal:open | Before modal opens (cancelable) | { modalId } |
| az:modal:opened | After modal fully opened | { modalId } |
| az:modal:close | Before modal closes (cancelable) | { modalId, trigger } |
| az:modal:closed | After modal fully closed | { modalId } |
| az:modal:action | Action button clicked | { modalId, action } |
Usage Example
// Prevent closing with unsaved changes
document.addEventListener('az:modal:close', (e) => {
if (e.detail.modalId === 'edit-form' && hasUnsavedChanges()) {
e.preventDefault();
showConfirmation();
}
});
// Handle action buttons
document.addEventListener('az:modal:action', (e) => {
if (e.detail.action === 'confirm') {
submitForm();
SuperModal.close(e.detail.modalId);
}
});
// Analytics
document.addEventListener('az:modal:opened', (e) => {
analytics.track('modal_opened', { modal: e.detail.modalId });
});
# Accessibility
SuperModal follows WAI-ARIA Dialog patterns.
Focus Trap
Tab key cycles through focusable elements within the modal
Focus Restoration
Focus returns to trigger element when modal closes
Escape to Close
Pressing Escape closes the modal (configurable)
ARIA Attributes
Automatic role="dialog", aria-modal, aria-labelledby
Background Inert
Content behind modal is marked inert for screen readers
Labeling Best Practices
<div data-az-component="super-modal"
data-az-modal-id="accessible-modal"
aria-labelledby="modal-title"
aria-describedby="modal-description">
<div data-az-modal-dialog>
<div data-az-modal-header>
<h2 id="modal-title">Edit Profile</h2>
</div>
<div data-az-modal-body>
<p id="modal-description">
Update your profile information below.
</p>
<!-- Form fields -->
</div>
</div>
</div>