Toast (Sonner)
Toast notifications via sonner - themed with the ai2 tokens and synced to next-themes. Render the Toaster once, call toast() anywhere.
"use client"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
export default function SonnerDemo() {
return (
<Button
variant="outline"
onClick={() => toast.success("Deployed to 3 regions")}
>
Show toast
</Button>
)
}Installation
Run the following command
npx shadcn@latest add @ai2/sonnerDependencies, the @ai2/tokens theme and the component file are installed together.
Install dependencies
npm install sonner@^2.0.7 next-themes@^0.4.6Add the cn util
lib/utils.tsimport { clsx, type ClassValue } from "clsx"
import { twMerge } from "tailwind-merge"
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs))
}
/* Adds a source-attribution ref param to a URL (the inspiration exports mark their
outbound links with an ai2.design attribution). An invalid URL is returned as is.
This file is SHOWN TO THE CONSUMER: the docs component pages render the source of
`cn` in a code block, so a Turkish comment here would reach every one of those
pages. Keep it English. */
export function withRef(url: string, ref = "ai2.design"): string {
try {
const u = new URL(url)
u.searchParams.set("ref", ref)
return u.toString()
} catch {
return url
}
}Copy the source code
components/ui/sonner.tsx"use client"
import type * as React from "react"
import { useTheme } from "next-themes"
import { Toaster as Sonner, type ToasterProps } from "sonner"
import { cn } from "@/lib/utils"
function Toaster({ className, ...props }: ToasterProps) {
const { theme = "system" } = useTheme()
return (
<Sonner
data-slot="toaster"
theme={theme as ToasterProps["theme"]}
className={cn("toaster group", className)}
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius)",
"--success-bg": "var(--success-soft)",
"--success-text": "var(--success-soft-foreground)",
"--success-border": "var(--success)",
"--error-bg": "var(--danger-soft)",
"--error-text": "var(--danger-soft-foreground)",
"--error-border": "var(--danger)",
"--warning-bg": "var(--warning-soft)",
"--warning-text": "var(--warning-soft-foreground)",
"--warning-border": "var(--warning)",
"--info-bg": "var(--info-soft)",
"--info-text": "var(--info-soft-foreground)",
"--info-border": "var(--info)",
} as React.CSSProperties
}
{...props}
/>
)
}
export { Toaster }Manual installs skip the @ai2/tokens theme - add the token CSS from the theming guide or the tone colors will be missing.
Usage
// app/layout.tsx - render the Toaster once near the root
import { Toaster } from "@/components/ui/sonner"
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
// anywhere else - trigger a toast
import { toast } from "sonner"
toast.success("Deployed to 3 regions")Render <Toaster /> exactly once near the root of your app, then import toast from sonner to trigger notifications from anywhere. The Toaster follows the active next-themes theme automatically.
Examples
Toast types
Tone colors
The wrapper maps sonner's success, error, warning and info variables to the ai2 tone tokens (soft background, soft foreground and a solid border per tone). Enable richColors on the Toaster (or per toast) and every typed toast renders in its tone; without it, all toasts use the neutral popover surface.
With action
Promise
toast.promise shows a loading toast, then swaps it for the success or error message when the promise settles.
Props
The ai2 Toaster adds no props of its own - it accepts every ToasterProps option from sonner (such as position, richColors and duration) and forwards them unchanged, with the theme preset from next-themes. With richColors enabled, the success, error, warning and info toasts use the ai2 tone tokens wired by the wrapper.
ai2 Toast (Sonner): notifications for React, themed by tokens
The ai2 Toaster is a shadcn-compatible toast setup for React, built on the sonner library and styled with the ai2 tokens through Tailwind CSS v4. You mount one Toaster near the root of your app, then call toast() from anywhere: success and error messages, descriptions, undo actions and promise-driven loading toasts all come from the same tiny API.
It ships through the shadcn registry format, so one CLI command, an MCP agent, or a copy-paste puts the source in your own project. You own the file; there is no runtime dependency on ai2 itself. The live example above is the exact component you get.
What is the ai2 Toaster?
It is a single part: Toaster, a thin wrapper around sonner's Toaster that does two things. First, it reads the active theme from next-themes via useTheme and passes it down, so toasts flip between light and dark with the rest of your app. Second, it maps sonner's CSS variables to the ai2 tokens: the neutral toast uses --popover, --popover-foreground, --border and --radius, and the success, error, warning and info toasts are wired to the ai2 tone tokens (soft background, soft foreground, solid border), which richColors picks up.
The trigger side is not a component at all: you import toast from sonner and call it from event handlers, server action callbacks or effects. The anatomy matches shadcn/ui exactly, so existing snippets and AI agents keep working without changes.
Why use it
- Mount once, toast anywhere: One Toaster in the root layout serves the whole app. Any module can import toast from sonner and fire a notification without context providers or prop drilling.
- Synced to next-themes: The wrapper reads the active theme with useTheme and passes it to sonner, so toasts follow light, dark and system preference without any extra wiring.
- Token-true styling: Toast background, text, border and radius are mapped to the ai2 popover, border and radius tokens, and with richColors the success, error, warning and info toasts use the ai2 tone tokens (soft background plus solid border), so notifications look like the rest of your surfaces, not like a third-party widget.
- Rich toast types built in: toast.success, toast.error, toast.warning, toast.info, descriptions, action buttons with callbacks and toast.promise for loading-to-result flows all ship with the sonner API.
- Agent-readable metadata: The registry item describes the setup and intended use in plain words, so an MCP agent can find, inspect and install it without guessing.
Features
- shadcn registry install: One command adds the component, the sonner and next-themes dependencies and the @ai2/tokens theme to your project.
- Promise toasts: toast.promise shows a loading toast and swaps it for the success or error message when the promise settles, ideal for deploys, saves and uploads.
- Action buttons: Pass an action with a label and onClick to render an inline button, the classic Undo pattern in one options object.
- Full ToasterProps passthrough: The wrapper adds no props of its own and forwards every sonner option: position, richColors, duration, closeButton and the rest.
- Stacking and swipe dismissal: sonner handles stacking, expand on hover and swipe to dismiss out of the box; the ai2 wrapper changes only the theming.
- TypeScript source: The file you install is typed end to end with sonner's own ToasterProps, so autocomplete covers the full API.
Production tips
- Render exactly one Toaster: Mount it once in the root layout, next to {children}. A second Toaster produces duplicate notifications for every toast call.
- Keep messages short and specific: One line that states what happened, like "Deployed to 3 regions". Put detail in the description option instead of stretching the title.
- Use toast.promise for async work: It ties loading, success and error to the actual promise, so users never see a success toast for an operation that later fails.
- Reserve actions for reversals: An inline Undo works well because it is optional. Do not put required next steps in a toast; it disappears on its own timer.
- Do not use toasts for critical errors: A toast can be missed. Validation errors belong next to the field, and blocking failures belong in an alert dialog that waits for the user.
Works with the rest of ai2
Toasts pair naturally with the rest of the registry. Fire them from an ai2 Button click after a save, confirm destructive flows with an ai2 Alert Dialog first and toast the result after, or close an ai2 Dialog form and announce success with a toast.
For state that should stay on screen, use an inline ai2 Alert instead, and show in-flight work with the ai2 Spinner while the promise toast is pending. Everything shares one token source, so notifications match your surfaces in both modes.