Skip to content
Guides

Advanced

How the pieces fit together under Qwik's resumability — and the edges worth knowing.

How it works

toast() writes to a small module-level store, the same design as sonner. Each <Toaster /> subscribes to it in the browser once Qwik starts (on the qinit event), so the Toaster's code only loads when the page boots — not when it scrolls into view — and toasts work even if it sits below the fold. A Toaster first rendered in the browser (behind a condition, or after a <Link> navigation) subscribes as soon as it renders, picks up the toasts already showing, and unsubscribes when it unmounts.

Toasts only exist in the browser. Server-side rendering always outputs an empty toaster.

Multiple toasters

// Unnamed: renders every toast without a toasterId
<Toaster />

// Named: renders only toasts sent to it
<Toaster id="canvas" position="top-left" />

toast("Saved");                                  // → default Toaster
toast("Layer duplicated", { toasterId: "canvas" }); // → "canvas"

Toasts without a toasterId go to the unnamed Toaster; a named Toaster ignores them.

Top layer

<dialog>.showModal(), fullscreen elements and popovers render in the browser's top layer, which no z-index can beat. With topLayer the Toaster opens itself as a popover="manual" while it has toasts, so they stay visible above modals.

<Toaster topLayer />
  • Off by default, for parity with sonner.
  • No-op in browsers without the Popover API — it falls back to normal stacking.
  • Each Toaster opens only while it has toasts of its own, so several can use topLayer.
  • The browser stacks the top layer in opening order: if a modal opens while toasts are showing, the Toaster's next toast brings it back above the modal.
  • With the headless entry, add the popover reset yourself.

See it next to a real dialog in the examples.

useSonner()

A reactive list of the active toasts, for building your own renderer, a badge, or a log. Like the Toaster, it subscribes once the page boots (or as soon as it renders in the browser) and unsubscribes when the component unmounts.

import { component$ } from "@qwik.dev/core";
import { useSonner } from "qwik-sonner";

export const ToastLog = component$(() => {
  const { toasts } = useSonner(); // Signal<Toast[]> of active toasts

  return (
    <ul>
      {toasts.value.map((t) => (
        <li key={t.id}>{typeof t.title === "string" ? t.title : "(custom)"}</li>
      ))}
    </ul>
  );
});

Outside components, read the store directly with toast.getToasts() and toast.getHistory().

SSR and resumability

Create toasts on the client

The store lives in module scope. On the server that scope is shared by every request, so a toast created during SSR would never reach the browser. Create toasts from event handlers, useVisibleTask$, or tasks guarded with isBrowser:

import { component$, isBrowser, useTask$ } from "@qwik.dev/core";

useTask$(({ track }) => {
  const status = track(() => connection.value);
  if (!isBrowser) return; // never toast during SSR
  if (status === "offline") toast.warning("You're offline");
});

Where to render the Toaster

In the root layout (src/routes/layout.tsx), so it's part of the first server-rendered HTML and persists across client-side navigations.

Accessibility

Live region
Toasts render in a <section aria-live="polite">, so screen readers announce them without stealing focus.
Hotkey
Alt + T (configurable with hotkey) moves focus to the toasts; focus returns where it was when they're gone.
Labels
containerAriaLabel, customAriaLabel and toastOptions.closeButtonAriaLabel.
Timers
Pause on hover, while pressing a toast, and while the tab is hidden.
Motion
Transitions and animations are off under prefers-reduced-motion.

TypeScript

import type {
  Toast,          // a toast as stored (ToastT)
  ExternalToast,  // options accepted by toast()
  ToasterProps,
  ToastClassnames,
  ToastToDismiss,
  Action,
  Position,       // "top-left" | "top-center" | … | "bottom-right"
  Theme,          // "light" | "dark" | "system"
  Direction,      // "rtl" | "ltr" | "auto"
  SwipeDirection, // "top" | "right" | "bottom" | "left"
  ToastTypes,
  ToastIcons,
  Offset,
  PromiseData,
} from "qwik-sonner"; // the same from "qwik-sonner/headless"

Known issues

None right now. The issues found in 2.0.0-beta.1 were fixed in 2.0.0-beta.2, and the dev-server stall with two toasts fired back to back went away with Qwik 2.0.0-rc.4 (qwik-sonner 2.0.0-beta.3) — see the changelog. Found one? Open an issue.