# Dialog A modal dialog using the native HTML `` element. Content is organized into optional title, description, body, and footer sections. Open/close is handled by client-side JS via delegated click events on `data-dialog-open` and `data-dialog-close` attributes. --- ## HTML structure ``` dialog[id, class=...] div.dialog-panel.relative.bg-background.p-6.rounded-lg.shadow-xl.w-full.max-w-md... button.absolute.top-4.right-4[data-dialog-close={id}] ← × close button h2.text-lg.font-semibold ← title (omitted when empty) p.text-sm.text-muted-foreground.mt-1 ← description (omitted when empty) div.mt-4 ← body content {content} div.mt-6.flex.justify-end.gap-2 ← footer (omitted when empty) {footer} ``` --- ## CSS mechanics | Class | Effect | |---|---| | `dialog::backdrop` (in `input.css`) | Semi-transparent black overlay behind the dialog | | `animate-in fade-in-0 zoom-in-95` | CSS entry animation when dialog opens | | `max-w-md w-full` | Responsive: full width on small screens, capped at `md` | | `overflow-y-auto max-h-[90vh]` | Scrollable body for tall content | --- ## JavaScript (delegated clicks in `components.js`) Set up once on `document` and works for all dialogs on the page, including those HTMX-swapped in. ### Open Any element with `data-dialog-open="myDialogId"` calls `document.getElementById('myDialogId').showModal()`. ### Close Any element with `data-dialog-close="myDialogId"` calls `document.getElementById('myDialogId').close()`. The `×` close button inside the dialog panel already has `data-dialog-close` set to the dialog's id. Clicking the `::backdrop` (outside the panel) also closes the dialog — the click handler checks whether the click target is the `` element itself. --- ## Constructor signature ```csharp public Dialog( string id, string content, string title = "", string description = "", string footer = "") ``` | Parameter | Description | |---|---| | `id` | Element id — must be unique per page; also used by `data-dialog-open` | | `content` | Raw HTML for the dialog body | | `title` | Optional heading at the top of the panel | | `description` | Optional subheading below the title | | `footer` | Optional raw HTML for the bottom button row | --- ## Usage examples ### Simple information dialog ```csharp // In the page component: Dialog = new Dialog( id: "about-dialog", title: "About BeepBoop", description: "A fast AOT-safe HTMX framework.", content: "

Version 1.0 — built with ❤️ and .NET 10.

", footer: """"""); ``` Trigger button anywhere on the page: ```html ``` ### Confirmation dialog ```csharp new Dialog( id: "confirm-delete", title: "Delete item", content: "

This action cannot be undone.

", footer: """ """) ``` ### HTMX-powered content reload ```csharp new Dialog( id: "user-detail", content: """
""") ``` The `revealed` trigger fires when the dialog becomes visible, loading content on demand. ### Embedding inside a page slot ```html $$DeleteDialog$$ ``` ```csharp public IHtmxComponent DeleteDialog { get; } public MyPage() { DeleteDialog = new Dialog( id: "confirm-delete", title: "Confirm deletion", content: "

Are you sure?

", footer: """"""); } protected override void RenderDeleteDialog(HtmxRenderContext ctx) => DeleteDialog.Render(ctx.Next()); ``` --- ## Tips and tricks - The `id` is used both on the `` element and in `data-dialog-open`/`data-dialog-close` — keep it unique per page. - The `×` close button is always rendered; `data-dialog-close` on footer buttons is optional but improves UX. - Use the native `` `close` event for any cleanup needed after dismissal: `document.getElementById('id').addEventListener('close', fn)`. - Dialog content, title, description, and footer are raw HTML — HTML-encode user-supplied values. - For dialogs that load content via HTMX, place the HTMX attributes on a child div inside `content` rather than on the `` element itself to avoid interfering with the dialog open/close lifecycle. - For dialogs that load content via HTMX, place the HTMX attributes on a child div inside `content` rather than on the `` element itself to avoid interfering with the dialog open/close lifecycle. --- ## Complete page example **`Templates/ItemsPage.htmx`** ```html

Items

$$ItemsTable$$ $$DeleteDialog$$
``` **`Templates/ItemsPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class ItemsPage : ItemsPageBase { private readonly IHtmxComponent _table; private readonly IHtmxComponent _deleteDialog; public ItemsPage(IEnumerable items, string? deleteTargetId = null) { // Build table with a per-row Delete button that opens the dialog _table = new Components.Table( headers: new[] { "Name", "Created", "Actions" }, rows: items.Select(item => new[] { System.Net.WebUtility.HtmlEncode(item.Name), item.CreatedAt.ToShortDateString(), $"""""", })); _deleteDialog = new Components.Dialog( id: "confirm-delete", title: "Delete item", content: """

This action cannot be undone.

""", footer: """ """); } protected override void RenderItemsTable(HtmxRenderContext ctx) => _table.Render(ctx.Next()); protected override void RenderDeleteDialog(HtmxRenderContext ctx) => _deleteDialog.Render(ctx.Next()); } ``` **GET + DELETE handlers** ```csharp [Handler] [MapGet("/items")] public static partial class GetItemsHandler { public record Query(); private static Task HandleAsync( Query _, HttpContext ctx, CancellationToken ct) { var items = new[] { new Item("1", "Widget A", DateTime.Today.AddDays(-10)), new Item("2", "Widget B", DateTime.Today.AddDays(-5)), }; return ctx.WriteHtmxPage(new ItemsPage(items), title: "Items"); } } [Handler] [MapDelete("/items")] public static partial class DeleteItemHandler { public record Command([property: FromForm] string ItemId); private static IResult HandleAsync([AsParameters] Command cmd, CancellationToken ct) { // Delete item from database… return Results.Ok(); // HTMX swaps the row out with outerHTML } } public record Item(string Id, string Name, DateTime CreatedAt); ```