Skip to content
Guides

Examples

Real-world patterns, each one live. Copy the snippet next to it — they only use the public API.

Undo a destructive action

Apply the change immediately and offer a way back, instead of asking for confirmation first.

Design review notes
Invoice #2026-104
Weekly digest
const remove = $((item: string) => {
  items.value = items.value.filter((i) => i !== item);

  toast("Conversation archived", {
    description: item,
    action: {
      label: "Undo",
      onClick$: $(() => (items.value = [...items.value, item])),
    },
  });
});

Submitting a form

toast.promise narrates the request; unwrap() lets the handler continue with the result. Try an address without @.

<form
  preventdefault:submit
  onSubmit$={async (_, form) => {
    const email = new FormData(form).get("email") as string;

    const result = toast.promise(subscribe(email), {
      loading: "Subscribing…",
      success: $((data: { email: string }) => `${data.email} is on the list`),
      error: $((err: Error) => err.message),
    });

    // unwrap() resolves/rejects like the original promise
    try {
      await result?.unwrap();
      form.reset();
    } catch {
      // the error toast is already showing
    }
  }}
>

Reporting progress

Update a loading toast by id as work advances, then settle it.

const id = toast.loading("Uploading report.pdf", { description: "0%" });

for (let p = 0; p <= 100; p += 20) {
  await new Promise((r) => setTimeout(r, 400));
  toast.loading("Uploading report.pdf", { id, description: `${p}%` });
}

toast.success("report.pdf uploaded", { id, description: "2.4 MB" });

Server action results

React to a routeAction$ result in a task, guarded with isServer: the toast store is a module-level singleton, so a toast created while rendering on the server would never reach the browser.

import { component$, isServer, useTask$ } from "@qwik.dev/core";
import { Form, routeAction$ } from "@qwik.dev/router";
import { toast } from "qwik-sonner";

export const useSaveProfile = routeAction$(async (data) => {
  // …persist
  return { ok: true, name: data.name as string };
});

export default component$(() => {
  const action = useSaveProfile();

  useTask$(({ track }) => {
    const result = track(() => action.value);
    // The toast store is module-global: never create toasts during SSR.
    if (!result || isServer) return;
    if (result.ok) toast.success(`Saved ${result.name}`);
    else toast.error("Could not save your profile");
  });

  return <Form action={action}>{/* fields */}</Form>;
});

Fetch requests

A Response that isn't ok becomes an error toast automatically — no need to throw yourself.

toast.promise(fetch("/api/report"), {
  loading: "Generating report…",
  success: "Report ready",
  // A Response with ok: false lands here with "HTTP error! status: 500"
  error: $((message: string) => message),
});

Above a modal dialog

<dialog>.showModal() lives in the browser's top layer, above any z-index. A Toaster with topLayer joins it there.

Edit event

A native modal dialog paints above every z-index — except the top layer.

// A second Toaster, promoted to the top layer
<Toaster id="over-modals" position="top-center" topLayer />

// Inside the <dialog>
toast.success("Saved", { toasterId: "over-modals" });

Several toasters

Route toasts to a named Toaster with toasterId — for example a top-center one for global announcements next to the default corner.

<Toaster />
<Toaster id="announcements" position="top-center" />

toast.info("Maintenance tonight", { toasterId: "announcements" });

Live toast counter

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

export const ToastCounter = component$(() => {
  const { toasts } = useSonner();
  return <span>{toasts.value.length} on screen</span>;
});

Following your app's theme

Pass a Signal to theme and flip it with the rest of your UI — this site does exactly that with its theme switch.

export default component$(() => {
  const theme = useSignal<"light" | "dark">("light");

  return (
    <>
      <Toaster theme={theme} />
      <button onClick$={() => (theme.value = theme.value === "dark" ? "light" : "dark")}>
        Toggle theme
      </button>
      <Slot />
    </>
  );
});