Files
Htmx/docs/Components/Toast.md
T
2026-05-05 23:55:26 +05:00

181 lines
6.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Toast
A small pop-up notification that appears in the corner of the screen, stays briefly, and then fades out on its own. Use it to give users confirmation after an action — "Saved!", "Error: could not connect", "Profile updated".
Unlike most components, toasts are triggered from **JavaScript**, not from the server-rendered template.
---
## Quick example
```js
window.showToast({
title: "Saved!",
variant: "success",
duration: 3000
});
```
---
## All the options
```js
window.showToast({
title: string, // required
description: string, // optional — shown below the title
variant: string, // "default" | "destructive" | "success"
duration: number // milliseconds before auto-dismiss (default: 4000)
})
```
| Option | What it does |
|---|---|
| `title` | The main notification text. |
| `description` | Optional secondary text below the title. |
| `variant` | `"default"` = neutral; `"destructive"` = red border (errors); `"success"` = green border. |
| `duration` | How long the toast stays visible before fading out. |
---
## Real-world examples
### Show a toast after an HTMX request completes
```js
document.body.addEventListener('htmx:afterRequest', function (e) {
if (e.detail.successful) {
window.showToast({ title: 'Changes saved', variant: 'success', duration: 3000 });
} else {
window.showToast({ title: 'Something went wrong', description: 'Please try again.', variant: 'destructive' });
}
});
```
### Trigger from the server via a response header
Add an `HX-Trigger` response header in your handler to fire a custom event:
```csharp
ctx.Response.Headers.Append("HX-Trigger",
"""{
"showToast": {
"title": "Profile updated",
"variant": "success",
"duration": 3000
}
}""");
```
Then listen for it on the client:
```js
document.body.addEventListener('showToast', function (e) {
window.showToast(e.detail);
});
```
This is the cleanest pattern for server-triggered toasts — the server decides the message and variant, the client handles the display.
---
## How it works
`window.showToast` creates a new `<div>` with the toast content and appends it to the `ToastViewport` container. A CSS animation slides it in. After `duration` ms, a fade-out animation plays and then the element is removed from the DOM. The dismiss button (×) triggers the same fade-out immediately.
You must have a `ToastViewport` component in your layout for toasts to appear. See [ToastViewport.md](./ToastViewport.md).
- 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
<div class="max-w-lg mx-auto py-10">
<h1 class="text-2xl font-bold mb-6">Contact us</h1>
<form hx-post="/contact"
hx-target="this"
hx-swap="outerHTML">
$$AntiforgeryToken$$
<div class="space-y-4 mb-6">
$$NameInput$$
$$EmailInput$$
$$MessageArea$$
</div>
$$SubmitBtn$$
</form>
</div>
```
**`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 = $"""<input type="hidden" name="{tokens.FormFieldName}" value="{tokens.RequestToken}">""".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<IResult> 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<byte>();
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 `<toast-viewport>` element listens for (see `ToastViewport`).
**`AppJsonSerializerContext.cs`**
```csharp
[JsonSerializable(typeof(PostContactHandler.Command), TypeInfoPropertyName = "ContactCommand")]
```