Files
Htmx/docs/Components/ToastViewport.md
T
2026-05-04 19:57:48 +05:00

4.8 KiB

ToastViewport

The fixed container that holds all Toast notifications. Place exactly one ToastViewport in your main layout (e.g. MainLayout.htmx). The viewport is invisible when empty and stacks toasts upward as they are added.


HTML structure

div[id={id}].toast-viewport.fixed.bottom-4.right-4.z-50.flex.flex-col-reverse.gap-2.w-80.pointer-events-none

CSS mechanics

Class Effect
fixed bottom-4 right-4 Anchored to the bottom-right corner of the viewport
z-50 Floats above all other content including dialogs and dropdowns
flex flex-col-reverse gap-2 New toasts appear on top; older ones push downward
w-80 Matches the default toast width
pointer-events-none The container itself doesn't capture clicks — toasts set pointer-events-auto individually

Constructor signature

public ToastViewport(string id = "toast-viewport")
Parameter Description
id Element id (default: "toast-viewport"). components.js queries #toast-viewport by default — only change this if you also update the JS lookup.

Usage examples

Place in MainLayout

<!-- MainLayout.htmx -->
<body class="...">
  <main>$$Body$$</main>
  $$ToastViewport$$
</body>
// MainLayout.htmx.cs
public IHtmxComponent ToastViewport { get; } = new ToastViewport();

protected override void RenderToastViewport(HtmxRenderContext ctx)
    => ToastViewport.Render(ctx.Next());

Custom id (advanced)

new ToastViewport(id: "notifications-container")

Then update the JS lookup:

// In components.js or a custom script:
const viewport = document.getElementById('notifications-container');

Custom position (bottom-left)

The position is set by Tailwind classes on the rendered element. To change position, subclass the component or pass extraClasses if supported, or override the toast-viewport class in your input.css:

.toast-viewport {
    bottom: 1rem;
    left: 1rem;
    right: auto;
}

Tips and tricks

  • Place ToastViewport once in the outermost layout — not inside HTMX swap targets, since HTMX replaces the target's contents and would remove the viewport.
  • The default id "toast-viewport" is hard-coded in components.js for the showToast lookup. If you rename it, update the JS too.
  • ToastViewport renders as an empty div — it has no visual presence until a toast is appended to it.
  • Multiple viewports on the same page are valid for different toast regions (e.g. top-right and bottom-right), but showToast will target whichever viewport id it is configured for.
  • Multiple viewports on the same page are valid for different toast regions (e.g. top-right and bottom-right), but showToast will target whichever viewport id it is configured for.

Complete page example

ToastViewport is a layout-level concern — it lives in MainLayout, not in individual page templates. The example below shows the full integration pattern.

Templates/MainLayout.htmx (excerpt)

<body class="min-h-screen bg-background text-foreground">
  $$NavBar$$
  <main class="container mx-auto px-4 py-8">
    $$Content$$
  </main>
  $$ToastViewport$$
  $$Scripts$$
</body>

Templates/MainLayout.htmx.cs (excerpt)

namespace Htmx.ApiDemo.Templates;

public sealed class MainLayout : MainLayoutBase
{
    private readonly IHtmxComponent _nav;
    private readonly IHtmxComponent _content;
    private readonly IHtmxComponent _viewport;

    public MainLayout(IHtmxComponent content, IHtmxComponent nav)
    {
        _nav      = nav;
        _content  = content;

        // Place one viewport at bottom-right for all pages
        _viewport = new Components.ToastViewport(
            id:       "toast-viewport",
            position: "bottom-right");
    }

    protected override void RenderNavBar(HtmxRenderContext ctx)       => _nav.Render(ctx.Next());
    protected override void RenderContent(HtmxRenderContext ctx)      => _content.Render(ctx.Next());
    protected override void RenderToastViewport(HtmxRenderContext ctx) => _viewport.Render(ctx.Next());
}

Triggering a toast from any handler

// Any POST handler can fire a toast without changing the ToastViewport markup.
ctx.Response.Headers["HX-Trigger"] =
    """{"showToast":{"title":"Saved!","description":"Your changes have been persisted."}}""";

Two-viewport layout (top-right errors + bottom-right successes)

_errorViewport   = new Components.ToastViewport(id: "error-viewport",   position: "top-right");
_successViewport = new Components.ToastViewport(id: "success-viewport", position: "bottom-right");

// Error toast
ctx.Response.Headers["HX-Trigger"] =
    """{"showToast":{"viewportId":"error-viewport","title":"Something went wrong."}}""";