# Toast A transient notification that appears in the bottom-right corner (or wherever `ToastViewport` is placed), auto-dismisses after a configurable duration, and can be dismissed manually. Toasts are triggered **client-side** via `window.showToast(...)` from JavaScript — they are not server-rendered components like most others. The `Toast` component class produces the initial toast markup for use as a static template or in the ToastViewport; in practice most toasts are created dynamically by the JS API. --- ## HTML structure (dynamically created by JS) ``` div.toast[role=alert, aria-live=polite, data-variant] div.flex.items-start.gap-3 div.flex-1 p.font-medium.text-sm ← title p.text-sm.text-muted-foreground ← description (omitted when empty) button.ml-auto[aria-label=Dismiss] ← × close button svg (×) ``` The outer `div.toast` is appended to the `ToastViewport` container by JS and removed after `duration` ms. --- ## CSS mechanics | Class | Effect | |---|---| | `toast` | Defined in `input.css`: `w-80 rounded-lg border bg-background p-4 shadow-lg pointer-events-auto` | | `toast-enter` / `toast-exit` | CSS keyframe animation classes applied by JS for slide-in/fade-out | | `data-variant="default"` | Border `border-border` | | `data-variant="destructive"` | Border `border-destructive`, title `text-destructive` | | `data-variant="success"` | Border `border-green-500` | --- ## JavaScript (`showToast` in `components.js`) ```js window.showToast({ title: "Operation complete", // required description: "All items saved.", // optional variant: "success", // "default" | "destructive" | "success" duration: 4000 // milliseconds before auto-dismiss }); ``` **Implementation steps:** 1. Build the toast `div` element with the classes and markup described above 2. Apply `toast-enter` class → CSS slide-in animation plays 3. Append to the `ToastViewport` (`#toast-viewport` by default, or the first `.toast-viewport` found) 4. After `duration` ms, apply `toast-exit` class → CSS fade-out animation plays 5. After fade-out completes, remove the element from the DOM 6. Dismiss button click runs the same fade-out + remove cycle immediately --- ## Constructor signature ```csharp public Toast( string title, string description = "", string variant = "default") ``` The constructor builds a static initial toast element. Most use-cases call `window.showToast(...)` from JS instead. | Parameter | Description | |---|---| | `title` | Required notification heading | | `description` | Optional body text | | `variant` | `"default"` / `"destructive"` / `"success"` | --- ## Usage examples ### Trigger from JavaScript after an HTMX event ```js document.body.addEventListener('htmx:afterRequest', function (e) { if (e.detail.successful) { window.showToast({ title: 'Saved', variant: 'success', duration: 3000 }); } else { window.showToast({ title: 'Error', description: 'Could not save.', variant: 'destructive' }); } }); ``` ### Trigger from a server response header Add a response header `HX-Trigger` in your handler: ```csharp ctx.Response.Headers.Append("HX-Trigger", """{"showToast":{"title":"Saved!","variant":"success","duration":3000}}"""); ``` Client-side listener: ```js document.body.addEventListener('showToast', function (e) { window.showToast(e.detail); }); ``` ### Server-rendered initial toast (rare) ```csharp // Used as a slot inside a page that always shows a greeting on first load: protected override void RenderWelcomeToast(HtmxRenderContext ctx) => new Toast("Welcome back!", "Your dashboard is ready.", "success").Render(ctx.Next()); ``` --- ## Tips and tricks - Always place a single `ToastViewport` in your main layout so toasts have a container to render into. See [ToastViewport.md](ToastViewport.md). - Use the `HX-Trigger` header pattern to trigger toasts from HTMX responses — it keeps toast logic on the server without requiring extra HTMX endpoints. - `duration: 0` means the toast never auto-dismisses — the user must click the × button. - Multiple toasts stack upward in the viewport (new ones appear above older ones) due to `flex-col-reverse` in `ToastViewport`. - For progress toasts that update as a background job runs, call `showToast` once and then use a reference to the element to update the description text. - For progress toasts that update as a background job runs, call `showToast` once and then use a reference to the element to update the description text. --- ## Complete page example **`Templates/ContactFormPage.htmx`** ```html

Contact us

$$AntiforgeryToken$$
$$NameInput$$ $$EmailInput$$ $$MessageArea$$
$$SubmitBtn$$
``` **`Templates/ContactFormPage.htmx.cs`** ```csharp namespace Htmx.ApiDemo.Templates; public sealed class ContactFormPage : ContactFormPageBase { private readonly IHtmxComponent _name; private readonly IHtmxComponent _email; private readonly IHtmxComponent _message; private readonly IHtmxComponent _submit; private readonly byte[] _afToken; public ContactFormPage(IAntiforgery af, HttpContext ctx) { var tokens = af.GetAndStoreTokens(ctx); _afToken = $"""""".ToUtf8Bytes(); _name = new Components.Input(id: "name", name: "name", label: "Name", placeholder: "Jane Smith"); _email = new Components.Input(id: "email", name: "email", label: "Email", placeholder: "jane@example.com", type: "email"); _message = new Components.Textarea(id: "message", name: "message", label: "Message", rows: 4); _submit = new Components.Button("Send message", type: "submit"); } protected override void RenderAntiforgeryToken(HtmxRenderContext ctx) => ctx.Writer.WriteUtf8(_afToken); protected override void RenderNameInput(HtmxRenderContext ctx) => _name.Render(ctx.Next()); protected override void RenderEmailInput(HtmxRenderContext ctx) => _email.Render(ctx.Next()); protected override void RenderMessageArea(HtmxRenderContext ctx) => _message.Render(ctx.Next()); protected override void RenderSubmitBtn(HtmxRenderContext ctx) => _submit.Render(ctx.Next()); } ``` **POST handler — triggers a toast via `HX-Trigger`** ```csharp [Handler] [MapPost("/contact")] public static partial class PostContactHandler { public record Command( [property: FromForm] string Name, [property: FromForm] string Email, [property: FromForm] string Message); private static Task HandleAsync( [AsParameters] Command cmd, HttpContext ctx, IAntiforgery af, CancellationToken ct) { // Persist / send message… // Re-render empty form so user can send another message var buf = new System.Buffers.ArrayBufferWriter(); new ContactFormPage(af, ctx).Render(new HtmxRenderContext(buf)); ctx.Response.Headers["HX-Trigger"] = """{"showToast":{"title":"Message sent!","description":"We'll get back to you soon."}}"""; return Task.FromResult(Results.Content( System.Text.Encoding.UTF8.GetString(buf.WrittenSpan), "text/html")); } } ``` > **Tip**: The `HX-Trigger` header fires the `showToast` custom event that the `` element listens for (see `ToastViewport`). **`AppJsonSerializerContext.cs`** ```csharp [JsonSerializable(typeof(PostContactHandler.Command), TypeInfoPropertyName = "ContactCommand")] ```