skip to the main content

Modal Dialog

The RzModal component provides a flexible way to display modal dialogs. It leverages Alpine.js for client-side state management (open/closed), transitions, focus trapping, and standard closing mechanisms (Escape key, backdrop click, close button). It's designed to be pre-rendered in your Blazor markup and triggered via client-side window events. Importantly, its internal body and footer areas have stable IDs (`BodyId`, `FooterId`), allowing HTMX to dynamically load or update content within the modal after it's opened. Modals can also be closed programmatically or via HTMX response headers triggering a specific event.

Basic Modal Triggered by Event

This example shows a standard modal pre-rendered on the page. Clicking the "Open Basic Modal" button dispatches a custom window event (`show-basic-modal`), which the `RzModal` component listens for via its `EventTriggerName` parameter.

Source
<RzButton onclick="window.dispatchEvent(new CustomEvent('show-basic-modal'))">
    Open Basic Modal
</RzButton>

<RzModal @ref="basicModalRef" EventTriggerName="show-basic-modal" Title="Basic Modal Example" Size="ModalSize.Large">
    <ChildContent>
        <p>This is the content of the basic modal.</p>
        <p>You can close it using the 'X' button, pressing Escape, or clicking the backdrop.</p>
    </ChildContent>
    <FooterContent>
        <div class="flex justify-end gap-2">
             <RzButton Variant="ButtonVariant.Alternate" x-on:click="closeModal">Close (Alpine)</RzButton>
             <RzButton Variant="ButtonVariant.Primary">Okay</RzButton>
        </div>
    </FooterContent>
</RzModal>

@code {
    RzModal basicModalRef = default!;
}

Customizing Appearance

You can customize the modal's appearance using parameters like `Size`, `ShowHeader`, `ShowCloseButton`, and providing custom `TitleContent`.

Source
<!-- Button to open small modal -->
<RzButton onclick="window.dispatchEvent(new CustomEvent('show-small-modal'))">
    Open Small (No Header)
</RzButton>

<!-- Small Modal without Header -->
<RzModal EventTriggerName="show-small-modal" Size="ModalSize.Small" ShowHeader="false">
    <ChildContent>
        <p>This modal has no header or default close button.</p>
        <div class="mt-4 text-right">
             <RzButton Variant="ButtonVariant.Alternate" x-on:click="closeModal">Close Me</RzButton>
        </div>
    </ChildContent>
</RzModal>

<!-- Button to open modal with custom title -->
<RzButton onclick="window.dispatchEvent(new CustomEvent('show-custom-title-modal'))">
    Open Custom Title
</RzButton>

<!-- Modal with Custom TitleContent -->
<RzModal EventTriggerName="show-custom-title-modal" Size="ModalSize.Medium">
    <TitleContent>
        <div class="flex items-center gap-2 font-semibold text-primary">
            <Blazicons.MdiIcon Svg="Blazicons.MdiIcon.Information" class="size-5"/>
            <span>Important Information</span>
        </div>
    </TitleContent>
    <ChildContent>
        <p>This modal uses custom `TitleContent` for the header.</p>
    </ChildContent>
</RzModal>

HTMX Content Loading

This demonstrates using HTMX to load content dynamically into the modal's body *after* the modal is opened via a client-side event. The button first triggers the modal open event, then initiates an HTMX GET request targeting the modal's specific `BodyId`. The content is loaded from a static Blazor page created for this demo.

Source
<RzButton hx-get="/demo/htmx-modal-sample"
          hx-target="#
" 
          hx-swap="innerHTML"
          onclick="window.dispatchEvent(new CustomEvent('show-htmx-load-modal'))">
    Open & Load Content via HTMX
</RzButton>

<RzModal @ref="htmxLoadModalRef" EventTriggerName="show-htmx-load-modal" Title="HTMX Loaded Content">
    <ChildContent>
        <div class="text-center p-4">
            <RzSpinner Size="Size.Large"/>
            <p class="mt-2">Loading content...</p>
        </div>
    </ChildContent>
</RzModal>

@code {
    RzModal htmxLoadModalRef = default!;
}
Source
@page "/demo/htmx-modal-sample"
@layout RizzyUI.Docs.Components.Layout.EmptyLayout
@using RizzyUI

<p>This content was loaded dynamically via an HTMX request!</p>
<RzAlert Variant="AlertVariant.Success">
    <AlertTitle>Success</AlertTitle>
    <AlertDescription>Content loaded successfully.</AlertDescription>
</RzAlert>

Closing with HTMX

Modals can be closed from the server by returning an HTMX response header that triggers the event specified in the `CloseEventName` parameter (default: `rz:modal-close`). This example shows a form inside the modal; upon successful submission, the server sends back the trigger header.

Source
<RzButton onclick="window.dispatchEvent(new CustomEvent('show-htmx-close-modal'))">
    Open HTMX Close Example
</RzButton>

<RzModal EventTriggerName="show-htmx-close-modal" Title="Close via HTMX Header" CloseEventName="rz:modal-close">
    <ChildContent>
        <form hx-post="/api/modal/htmx-close-action"
              hx-target="this"
              hx-swap="outerHTML">
            <p>Submit this form. The server response will include an HX-Trigger header to close this modal.</p>
            <div class="text-right mt-4">
                <RzButton type="submit" Variant="ButtonVariant.Success">Submit & Close</RzButton>
            </div>
        </form>
    </ChildContent>
</RzModal>
Source
[HttpPost("/api/modal/htmx-close-action")]
public IResult CloseModalAction()
{
    // Process form data if needed...

    // Add the HX-Trigger header to close the modal
    // Uses the event name 'rz:modal-close' (matches RzModal default or explicit parameter)
    HttpContext.Response.Htmx(h => h.Trigger(RizzyUI.Constants.Events.ModalClose));

    // Can return swapped content for the form, or just Ok/NoContent if only closing
    return Ok("<p>Form Submitted! Modal closed by server.</p>"); 
}

RzModal Component Parameters

The following table summarizes the key parameters for the RzModal component.

ParameterTypeDefaultDescription
Idstring(Generated)Read-only. The unique ID for the main modal `x-data` container div. Passed in event details.
BodyIdstring(Generated)Read-only. ID for the modal body `div`. Use this as `hx-target` for HTMX swaps into the body. Passed in `rz:modal-initialized` event detail.
FooterIdstring(Generated)Read-only. ID for the modal footer `div`. Use this as `hx-target` for HTMX swaps into the footer. Passed in `rz:modal-initialized` event detail.
EventTriggerNamestring"" (Empty String)Name of the `window` event that will trigger the modal to open.
CloseEventNamestringrz:modal-closeName of the `window` event the modal listens for to close itself (e.g., triggered by HTMX response header).
Titlestring?nullOptional title text for the modal header. Ignored if `TitleContent` is provided.
SizeModalSizeModalSize.MediumSpecifies the maximum width of the modal dialog.
TitleContentRenderFragment?nullOptional custom content for the modal header title area. Overrides `Title`.
ChildContentRenderFragment?nullThe main content displayed in the modal body. Can be targeted by HTMX using `BodyId`.
FooterContentRenderFragment?nullOptional content displayed in the modal footer. Can be targeted by HTMX using `FooterId`.
CloseOnEscapebooltrueIf true, the modal closes when the Escape key is pressed. Configures the Alpine component.
CloseOnClickOutsidebooltrueIf true, the modal closes when clicking on the backdrop. Configures the Alpine component.
ShowHeaderbooltrueIf true, the modal header section (including title and close button container) is rendered.
ShowCloseButtonbooltrueIf true (and `ShowHeader` is true), the default 'X' close button is rendered in the header.

JavaScript & Alpine.js API

The modal's interactivity is primarily managed by the `rzModal` Alpine.js component. You can interact with it programmatically or listen to its lifecycle events.

Alpine Component (`x-data="rzModal"`)

Attached to the modal's root `div` (inside the teleport template). It reads configuration from `data-*` attributes set by the Blazor component.

Property/MethodDescription
modalOpenBoolean state variable indicating if the modal is currently visible.
eventTriggerNameName of the window event that triggers the modal to open.
closeEventNameName of the window event that triggers the modal to close.
closeOnEscapeBoolean that controls whether the modal closes when Escape key is pressed.
closeOnClickOutsideBoolean that controls whether the modal closes when clicking outside the dialog.
modalId, bodyId, footerIdRead-only properties holding the generated IDs for the modal elements, available in dispatched event details.
openModal(event = null)Method to programmatically open the modal. Dispatches `rz:modal-before-open` (cancelable) and if not prevented, sets modalOpen to true.
closeModal()Public method to close the modal from the close button. Internally calls closeModalInternally with reason 'button'.
closeModalInternally(reason)Internal method that handles closing the modal with a specific reason ('button', 'escape', 'backdrop', 'event'). Dispatches `rz:modal-before-close` (cancelable).
handleClickOutside()Method called by click.outside event handler. Checks closeOnClickOutside setting before closing.
notModalOpen()Helper method that returns !modalOpen, useful for x-show directives.

Custom Events

The component dispatches events on its root element (`#@Id`) during its lifecycle. You can listen for these using `window.addEventListener` or Alpine's `x-on`.

Event NameDetail StructureCancelable?Description
rz:modal-initialized`{ modalId, bodyId, footerId }`NoFired once during Alpine `init`.
rz:modal-before-open`{ modalId, originalEvent }`YesFired before opening. Call `event.preventDefault()` to stop.
rz:modal-after-open`{ modalId }`NoFired after opening transitions complete.
rz:modal-before-close`{ modalId, reason }`YesFired before closing. Call `event.preventDefault()` to stop. `reason` indicates cause ('escape', 'backdrop', 'button', 'event').
rz:modal-after-close`{ modalId }`NoFired after closing transitions complete.

Example: `window.addEventListener('rz:modal-after-open', (e) => { console.log('Modal opened:', e.detail.modalId); });`

Triggering Programmatically

You can open or close the modal from JavaScript by dispatching the window events specified in the `EventTriggerName` or `CloseEventName` parameters respectively.

Source
// To open a modal listening for 'show-my-modal'
window.dispatchEvent(new CustomEvent('show-my-modal', { detail: { /* optional data */ } }));

// To close a modal listening for the default '
'
window.dispatchEvent(new CustomEvent('
'));

// To close a specific modal listening for 'close-my-modal-xyz'
window.dispatchEvent(new CustomEvent('close-my-modal-xyz', { detail: { /* optional data */ } }));

Alpine.js Direct Access

You can directly interact with the modal from other Alpine.js components or event handlers:

Source
// In an event handler or another Alpine component
// Get the modal's Alpine data context
const modalElement = document.getElementById('my-modal-id');
const modalData = Alpine.$data(modalElement);

// Open or close programmatically
modalData.openModal();
modalData.closeModal();

// Check if modal is open
const isOpen = modalData.modalOpen;