Skip to content
Component installs need the registry setup— the Base UI shadcn project, the @vegastack namespace and the Cloudflare Access service token.
VegaStack Design

Sonner

The sonner toaster, themed onto the token contract — an alternative notification engine with its own imperative API.

Status
stable
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/sonner

It 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

Toaster

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?): void

Three 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 Notifications plus sonner's hotkey hint, and is reachable with that hotkey (Alt+T by default, hotkey to 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-text and --normal-border, which are bound to popover, popover-foreground and border — so it inherits the theme rather than carrying sonner's own palette.
ContractStates tested
Behaviourdefault, open, closed, expanded, promise
Accessibilitynative-or-base-ui-semantics, browser-accessibility-test, keyboard-navigation
Visualdefault, success, info, warning, error, loading

Do / Don't

Do
Pick ONE notification engine for the app and mount only its toaster: Sonner when you want its stacking, its hotkey-reachable region and its `toast.*` API, and are happy for the surface to be themed from the outside.
Don't
Mount both Sonner and Toast. They are two engines for the same job, each with its own live region and its own fixed viewport, so together they announce twice and overlap on screen.

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.8 renders ONE aria-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.tsx asserts that region exists before any toast is fired, so the day the engine changes the test fails.
  • DOC-1registry-stamp.mjs writes the provenance header the three-copy model needs, and prettier reflows the file; neither carries a decision of its own. There is no cn hunk here: upstream's sonner imports no cn.

On this page