Dialog
The RzDialog component suite provides a flexible way to display modal dialogs. It's a composable system built from components like DialogTrigger, DialogContent, DialogHeader, and more. It leverages Alpine.js for client-side state management, transitions, focus trapping, and standard closing mechanisms. Its internal body and footer areas have stable IDs, allowing HTMX to dynamically load or update content within the dialog after it's opened.
Under the Hood
This component uses an Alpine.js component (x-data="rzModal") to manage its state and interactions. It utilizes <template x-teleport="body"> to ensure the dialog is rendered at the end of the document body, avoiding potential z-index issues.
Basic Dialog
This example shows a standard dialog. The DialogTrigger component wraps a button that, when clicked, opens the DialogContent.
Basic Dialog Example
This is the content of the basic dialog. You can close it using the 'X' button, pressing Escape, or clicking the backdrop.
This is the main body content.
<RzDialog>
<DialogTrigger AsChild>
<RzButton Variant="ThemeVariant.Primary">Open Basic Dialog</RzButton>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Basic Dialog Example</DialogTitle>
<DialogDescription>
This is the content of the basic dialog. You can close it using the 'X' button, pressing Escape, or clicking the backdrop.
</DialogDescription>
</DialogHeader>
<div class="p-6">
<p>This is the main body content.</p>
</div>
<DialogFooter>
<DialogClose AsChild>
<RzButton Variant="ThemeVariant.Accent">Close</RzButton>
</DialogClose>
<RzButton Variant="ThemeVariant.Primary">Okay</RzButton>
</DialogFooter>
</DialogContent>
</RzDialog>Customizing Appearance
You can customize the dialog's appearance using parameters like Size on DialogContent, or by omitting components like DialogHeader.
This dialog has no header and the default close button is hidden.
Important Information
This dialog uses custom markup inside the DialogTitle.
<!-- Small Dialog without Header -->
<RzDialog>
<DialogTrigger AsChild>
<RzButton>Open Small (No Header)</RzButton>
</DialogTrigger>
<DialogContent Size="ModalSize.Small" ShowCloseButton="false">
<div class="p-6">
<p>This dialog has no header and the default close button is hidden.</p>
<div class="mt-4 text-right">
<DialogClose AsChild>
<RzButton Variant="ThemeVariant.Accent">Close Me</RzButton>
</DialogClose>
</div>
</div>
</DialogContent>
</RzDialog>
<!-- Dialog with Custom TitleContent -->
<RzDialog>
<DialogTrigger AsChild>
<RzButton>Open Custom Title</RzButton>
</DialogTrigger>
<DialogContent Size="ModalSize.Medium">
<DialogHeader>
<DialogTitle>
<div class="flex items-center gap-2 font-semibold text-primary">
<Blazicon Svg="MdiIcon.Information" class="size-5"/>
<span>Important Information</span>
</div>
</DialogTitle>
</DialogHeader>
<div class="p-6">
<p>This dialog uses custom markup inside the <code>DialogTitle</code>.</p>
</div>
</DialogContent>
</RzDialog>HTMX Content Loading
This demonstrates using HTMX to load content dynamically into the dialog's body. The DialogTrigger opens the dialog, and its child RzButton has HTMX attributes to fetch content, targeting the dialog's specific BodyId.
HTMX Loaded Content
Loading content...
<RzDialog @ref="htmxLoadDialogRef">
<DialogTrigger AsChild>
<RzButton hx-get="/demo/htmx-dialog-sample"
hx-target="@($"#{htmxLoadDialogRef.BodyId}")"
hx-swap="innerHTML"
hx-indicator="@($"#{htmxLoadDialogRef.BodyId}-spinner")">
Open & Load Content via HTMX
</RzButton>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>HTMX Loaded Content</DialogTitle>
</DialogHeader>
<div id="@htmxLoadDialogRef.BodyId" class="p-6">
<div class="text-center p-4">
<RzSpinner Id="@($"#{htmxLoadDialogRef.BodyId}-spinner")" Size="Size.Large"/>
<p class="mt-2">Loading content...</p>
</div>
</div>
</DialogContent>
</RzDialog>
@code {
RzDialog htmxLoadDialogRef = default!;
}@page "/demo/htmx-dialog-sample"
@layout RizzyUI.Docs.Components.Layout.EmptyLayout
@using RizzyUI
<p>This content was loaded dynamically via an HTMX request!</p>
<RzAlert Variant="ThemeVariant.Success">
<AlertTitle>Success</AlertTitle>
<AlertDescription>Content loaded successfully.</AlertDescription>
</RzAlert>Closing with HTMX
Dialogs 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 dialog; upon successful submission, the server sends back the trigger header.
Static Documentation Limitation
This specific "Close via HTMX Header" example requires server-side processing to send the `HX-Trigger` response header. Since this documentation is rendered as static HTML, clicking the "Submit & Close" button below will not actually close the dialog in this demo environment. It demonstrates the client-side setup and the expected server-side response.
Close via HTMX Header
<RzDialog>
<DialogTrigger AsChild>
<RzButton>Open HTMX Close Example</RzButton>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Close via HTMX Header</DialogTitle>
</DialogHeader>
<div class="p-6">
<form hx-post="/api/dialog/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 dialog.</p>
<div class="text-right mt-4">
<RzButton type="submit" Variant="ThemeVariant.Success">Submit & Close</RzButton>
</div>
</form>
</div>
</DialogContent>
</RzDialog>[HttpPost("/api/dialog/htmx-close-action")]
public IResult CloseDialogAction()
{
// Process form data if needed...
// Add the HX-Trigger header to close the dialog
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! Dialog closed by server.</p>");
}Accessibility
Pattern: Modal dialog aligned with the WAI-ARIA APG dialog pattern. Dialog semantics are applied on DialogContent through the internal modal primitive with role="dialog", aria-modal="true", and name/description wiring.
Accessible naming: Prefer DialogTitle so the dialog is labelled via aria-labelledby. If no visible title is rendered, provide DialogContent.AriaLabel as the fallback accessible name. Optional supporting text can be wired with DialogDescription (aria-describedby).
| Key | Behavior |
|---|---|
Tab | Moves forward through focusable elements inside the open dialog and wraps from last to first. |
Shift+Tab | Moves backward through focusable elements and wraps from first to last. |
Escape | Dismisses the topmost dialog layer via dismissableLayer, then restores focus to the invoking trigger when still connected. |
Outside click | Dismisses only the topmost dismissable layer. Nested overlays close before parent layers. |
Focus management: On open, focus enters the dialog using focusScope. Tabbing is trapped to dialog descendants while open. On close, focus is restored to the invoking trigger; if unavailable, focus falls back to the dialog container.
Screen reader behavior: The dialog announces through semantic role/name/description and focus transition. No dedicated live announcement channel is used for open/close transitions.
SSR and enhanced navigation: Dialog behavior is initialized client-side and should be re-initialized after enhanced navigation or partial re-rendering when markup is replaced.
Known limitations: This page documents modal usage. Complex nested overlay scenarios inside the dialog (for example popovers/menus with independent focus scopes) are validated separately in dedicated overlay-integration tests.
<RzDialog>
<DialogTrigger AsChild>
<RzButton Variant="ThemeVariant.Primary">Open Preferences</RzButton>
</DialogTrigger>
<DialogContent>
<DialogHeader>
<DialogTitle>Account preferences</DialogTitle>
<DialogDescription>Adjust account settings and save when complete.</DialogDescription>
</DialogHeader>
<div class="p-6">...content...</div>
<DialogFooter>
<DialogClose AsChild>
<RzButton Variant="ThemeVariant.Accent">Close</RzButton>
</DialogClose>
</DialogFooter>
</DialogContent>
</RzDialog><RzDialog>
<DialogTrigger AsChild>
<RzButton>Open Support Details</RzButton>
</DialogTrigger>
<DialogContent AriaLabel="Support details" ShowCloseButton="true">
<div class="p-6">
Support workflow details without a visible heading.
</div>
</DialogContent>
</RzDialog>Accessibility test reference: src/RizzyUI.Docs/tests/accessibility/dialog.a11y.spec.ts.
Component Parameters
The following tables summarize primary dialog component parameters.
RzDialog
| Property | Description | Type | Default |
|---|---|---|---|
ChildContent | Contains trigger/content composition for the dialog. | RenderFragment | Required |
EventTriggerName | Window event name that opens the dialog. | string | string.Empty |
CloseEventName | Window event name used to close the dialog. | string | Constants.Events.DialogClose |
CloseOnEscape | Closes dialog when Escape is pressed. | bool | true |
CloseOnClickOutside | Closes dialog when the backdrop is clicked. | bool | true |
DialogContent
| Property | Description | Type | Default |
|---|---|---|---|
ChildContent | Content displayed inside the dialog surface. | RenderFragment | Required |
ShowCloseButton | Toggles the built-in close button. | bool | true |
Size | Controls dialog width preset. | ModalSize | ModalSize.Large |
AriaLabel | Optional explicit accessible name used when no DialogTitle is rendered. | string? | null |
Alpine API
Dialog interactivity is implemented by Alpine x-data component rzModal.
| Method | Parameters | Description |
|---|---|---|
openModal | event? | Opens the dialog and dispatches lifecycle events. |
closeModal | None | Closes the dialog using the normal close pipeline. |
handleClickOutside | event | Handles backdrop/pointer interactions to close when allowed. |