Skip to content
Getting started

Migrating from v1

qwik-sonner 2 follows your app to Qwik 2 and catches up with sonner 2. Most apps only change the install command; the breaking changes below are small and mechanical.

v1 (1.0.3)
v2 (2.0.0-beta.3)
@builder.io/qwik 1.x@qwik.dev/core 2.x (peer dependency)ESM + CommonJSESM only1 dependency (DOMPurify)0 dependenciessonner 1.x behavioursonner 2.0.8 behaviour

Steps

1. Move your app to Qwik 2

Follow Qwik's own upgrade guide first: packages move from @builder.io/* to @qwik.dev/*. qwik-sonner 2 doesn't run on Qwik 1.

2. Install v2

npm install qwik-sonner@beta

Imports stay the same — qwik-sonner and qwik-sonner/headless.

3. Apply the breaking changes

Search your code for the patterns below.

Breaking changes

Strings render as text, not HTML

v1 parsed title and description strings as HTML through DOMPurify. v2 renders them as text, like sonner. Pass JSX for formatting:

// v1 — string parsed as (sanitized) HTML
toast("<strong>Saved</strong> to drafts");
// v2 — strings are text; use JSX for markup
toast(<span><strong>Saved</strong> to drafts</span>);

Icons are JSX

icons, a toast's icon and the deprecated loadingIcon take JSXOutput instead of a Component.

// v1 — a component
<Toaster icons={{ success: CheckIcon }} />
toast("Done", { icon: CheckIcon });
// v2 — JSX
<Toaster icons={{ success: <CheckIcon /> }} />
toast("Done", { icon: <CheckIcon /> });

ESM only

The *.qwik.cjs builds are gone. Qwik apps are ESM already, so this only matters for tooling that require()s the package.

New defaults

  • Viewport offset: 32px → 24px, plus a new mobileOffset of 16px below 600px.
  • Swipe distance to dismiss: 20px → 45px (fast flicks still count).
  • Markup and styles follow sonner 2's data-sonner-* contract. Custom CSS that targeted v1's markup may need new selectors — see data attributes.
  • important: true no longer makes a toast aria-live="assertive". Every toast is announced through the toaster's polite live region, as in sonner 2.
  • The toast list's theme attribute is data-sonner-theme (was data-theme), and theme="system" no longer reads or writes localStorage.theme.
// Keep v1's spacing
<Toaster offset="32px" />

Deprecated

  • pauseWhenPageIsHidden — has no effect; timers always pause while the tab is hidden.
  • loadingIcon — use icons={{ loading: <Spinner /> }}.

Toaster placement

v1 recommended rendering the Toaster before the rest of the page to avoid rendering glitches. v2 subscribes when Qwik boots, so it can go anywhere in the root layout — but keep it in a server-rendered layout (see known issues).

// v1 needed the Toaster before other content (issue #13).
// v2: anywhere in the root layout works.
export default component$(() => (
  <>
    <Slot />
    <Toaster />
  </>
));

Worth adopting

  • Signals for position, theme, richColors, dir and invert.
  • topLayer for toasts above native dialogs.
  • Multiple toasters with id / toasterId.
  • toast.promise(...).unwrap(), useSonner(), toast.getToasts().
  • swipeDirections, per-toast richColors, testId.