Sonner
The sonner toaster, themed onto the token contract — an alternative notification engine with its own imperative API.
- Status
- Since
0.10.0- Accessibility pattern
- engine-owned polite live region
Last updated
Install
Add Sonner from the VegaStack registry. The CLI verifies the item's integrity hash before writing it.
pnpm dlx shadcn@latest add @vegastack/sonnerIt also adds the sanctioned engines to your package.json: sonner (toast stack and imperative notification engine), next-themes (resolved-theme source for the engine colour scheme).
Usage
Toaster is sonner's toaster with our tokens bound to its CSS variables and our lucide glyphs
bound to its icon slots. Mount one at the app root; the toast function comes from sonner
itself, not from this item.
import { toast } from "sonner";
import { Toaster } from "@/components/ui/sonner";
// app/layout.tsx
<body>
{children}
<Toaster />
</body>;
// anywhere
toast("Event created", {
description: "Sunday, December 3 at 9:00 AM",
});Scope
Sonner is the second notification engine in this system, alongside toast. Both are sanctioned
and they do the same job with different machinery: toast is Base UI's stack rendered by our own
parts, so its surface is composed from Toast, ToastContent, ToastAction and the rest and every
build rule applies to it directly; sonner renders its own surface, which this item themes from the
outside through CSS variables and an icon map. Pick one per app.
Anatomy
Examples
Types
toast.success, toast.info, toast.warning, toast.error and toast.loading each render their
own lucide glyph from the icons map this item passes, so the type is a shape as well as a colour.
toast(...) with no type is the plain variant.
Action
action takes a label and an onClick. toast.dismiss(id) closes one toast; with no argument it
closes them all.
toast("Event created", {
description: "Sunday, December 3 at 9:00 AM",
action: { label: "Undo", onClick: () => toast.dismiss() },
});Promise
toast.promise drives one toast through loading, success and error. success and error may be a
function of the resolved value or the rejection.
API Reference
SonnerToaster adds no props of its own — it accepts everything the underlying element or Base UI primitive accepts (className, ref, ARIA attributes, event handlers).
This item exports Toaster only. The imperative API — toast, toast.success, toast.promise,
toast.dismiss and the rest — is sonner's own and is imported from sonner:
toast(message, options): string | number
toast.success | info | warning | error | loading (message, options)
toast.promise(promise, { loading, success, error })
toast.dismiss(id?): voidThree of Toaster's props are already set by this item and should be left alone unless you mean to
replace the theming: theme (bound to next-themes), icons (the five lucide glyphs) and style
(the token bridge — --normal-bg, --normal-text, --normal-border, --border-radius). Everything
else — position, expand, duration, visibleToasts, closeButton, offset, dir,
richColors — is yours. id on the toaster plus toasterId on a call scopes toasts to one
toaster, which is how the previews on this page keep four toasters apart.
Accessibility
- sonner renders one
<section aria-live="polite">from the toaster's first render and reuses it for every toast, so the region is being observed before the first toast arrives (A11Y-3, A11Y-4). That region is the engine's; this item adds none of its own, because a second one would announce every toast twice. - The region is labelled
Notificationsplus sonner's hotkey hint, and is reachable with that hotkey (Alt+T by default,hotkeyto change it) rather than sitting in the tab order. - Each type renders a distinct glyph, so the type survives a monochrome or colour-blind rendering.
- The surface takes its colours from
--normal-bg,--normal-textand--normal-border, which are bound topopover,popover-foregroundandborder— so it inherits the theme rather than carrying sonner's own palette.
| Contract | States tested |
|---|---|
| Behaviour | default, open, closed, expanded, promise |
| Accessibility | native-or-base-ui-semantics, browser-accessibility-test, keyboard-navigation |
| Visual | default, success, info, warning, error, loading |
Do / Don't
Deviations
Upstream's file plus packages/ui/upstream/patches/sonner.patch. Every hunk:
- A11Y-3, A11Y-4 — NO HUNK, and this is where that claim is recorded rather than assumed.
sonner@2.0.8renders ONEaria-live="polite"region from the toaster's first render and reuses it for every toast, which is A11Y-4's mounted-empty region and A11Y-3's polite-by-default policy.sonner.test.tsxasserts that region exists before any toast is fired, so the day the engine changes the test fails. - DOC-1 —
registry-stamp.mjswrites the provenance header the three-copy model needs, and prettier reflows the file; neither carries a decision of its own. There is nocnhunk here: upstream's sonner imports nocn.