Next.js Toast Notifications with Drivn
Add toast notifications to a Next.js App Router app: mount one client Toaster in the root layout and fire toasts from components and after Server Actions.
Toast notifications are the small, self-dismissing messages that confirm a save, flag an error, or show a loading state without stealing the page. In a Next.js App Router app the hard part is not the message — it is the wiring: the toast container has to mount once on the client, sit above every route, and be callable from anywhere, including the handlers that run after a Server Action resolves. Drivn's Toast handles that boundary for you. It is a 'use client' Toaster that wraps Sonner — the headless toast engine — and re-exports Sonner's toast() function unchanged, so the queue, stacking, swipe-to-dismiss, and timing all come from a proven library while Drivn supplies the defaults.
After npx drivn add toast the component lives in src/components/ui/toast.tsx as source you own. It pre-wires five lucide-react icons — success, error, info, warning, and a spinning loader — and themes the surface with Drivn's own --card, --card-foreground, and --border tokens at a 12px radius. This guide installs Drivn in a Next.js 16 project, mounts the Toaster once in the root layout, fires toasts from client components and after Server Actions, and restyles the container. For the full reference see the Toast docs; for the shadcn/ui comparison see Drivn vs shadcn/ui Toast.
Install in a Next.js 16 project
Drivn installs through a small CLI that writes the component source straight into your repository — there is no runtime package to version-lock beyond Sonner itself. From the root of your Next.js 16 project run npx drivn add toast. The Toast wrapper depends on the sonner and lucide-react packages, so the CLI notes them; if they are not already installed, add them once and you are set. The command writes a single toast.tsx under your UI directory — the CLI reference documents every flag, including targeting a custom path or adding several components at once. After install you own the file, so a future Drivn release will not overwrite your edits, and the whole wrapper — the icon map and the token-based style object — is a few lines you can read top to bottom. Commit the change for a clean baseline before wiring it in.
1 # from the root of your Next.js 16 project 2 npx drivn add toast
Mount the Toaster once in the root layout
A toast container should mount exactly once, high enough in the tree to sit above every route. In the App Router that place is the root layout. The Toaster is a client component — its source opens with 'use client' because Sonner manages a portal and a queue — but you render it directly inside a Server Component layout, because a Server Component may render a client child. Import Toaster into app/layout.tsx and drop it just before the closing </body>, after {children}. That single mount serves every page; you never render a second Toaster. Because it draws nothing until a toast fires, its position in the layout affects only stacking order, not layout flow. See the installation guide for the surrounding project setup.
1 import { Toaster } from '@/components/ui/toast' 2 3 export default function RootLayout({ 4 children, 5 }: { 6 children: React.ReactNode 7 }) { 8 return ( 9 <html lang="en"> 10 <body> 11 {children} 12 <Toaster /> 13 </body> 14 </html> 15 ) 16 }
Fire toasts from client components
With the Toaster mounted, any client component can import toast and call it from an event handler. toast('Changes saved') shows a neutral message; toast.success, toast.error, toast.info, and toast.warning each render with the matching pre-wired lucide icon — a green check, a red cross, an info circle, a warning triangle. Pass a second argument for a description or an action button, exactly as the Sonner API describes, because Drivn re-exports toast unchanged. The one rule the App Router adds: the call must originate on the client, so the component that calls toast needs a 'use client' directive. A Server Component cannot call toast directly — it has no browser to render into — which is why toasts fire from handlers, effects, or callbacks. Drop a Button beside it for a save-with-feedback flow, and see the Toast examples for more variants.
1 'use client' 2 3 import { toast } from '@/components/ui/toast' 4 import { Button } from '@/components/ui/button' 5 6 export function SaveButton() { 7 return ( 8 <Button 9 variant="secondary" 10 onClick={() => toast.success('Changes saved')} 11 > 12 Save 13 </Button> 14 ) 15 }
Fire toasts after a Server Action
The common Next.js flow is a form that posts to a Server Action, then confirms the result. Because a Server Action runs on the server, it cannot fire a toast itself — but the client component that invokes it can. Wrap the action in a client form handler: await the action, then branch on its return to call toast.success or toast.error. For a pending state while the action is in flight, toast.loading returns an id you pass to toast.success(msg, { id }) to swap the spinner for the result in place — the spinning Loader2 icon Drivn pre-wires is exactly what shows during that loading phase. This keeps the mutation on the server and the feedback on the client, which is the boundary the App Router is built around. The Toast reference lists every method the re-exported toast supports.
1 'use client' 2 3 import { toast } from '@/components/ui/toast' 4 import { saveProfile } from './actions' 5 6 export function ProfileForm() { 7 return ( 8 <form 9 action={async (formData) => { 10 const id = toast.loading('Saving…') 11 const res = await saveProfile(formData) 12 if (res.ok) toast.success('Profile updated', { id }) 13 else toast.error('Could not save', { id }) 14 }} 15 > 16 {/* fields + submit */} 17 </form> 18 ) 19 }
Customize icons, position, and theming
Everything the container renders is set in one place — the Toaster wrapper you own. Change where toasts appear by passing a position prop such as top-center or bottom-left; the default is Sonner's bottom-right. The icon map and the theming are already wired in the source: five lucide-react icons keyed by variant, and a style object that maps Sonner's CSS variables onto Drivn's design tokens — --normal-bg to var(--card), the text to var(--card-foreground), the border to var(--border), and a --border-radius of 12px. Because those read your Tailwind theme tokens, the toast surface re-themes for dark and light mode automatically when you recolor them in theming. To swap an icon or the radius, edit the object in place — there is no config file and no variant map to thread through. The block below is copied verbatim from toast.ts.
1 function Toaster() { 2 return ( 3 <Sonner 4 icons={{ 5 success: ( 6 <CheckCircle2 className="w-4 h-4" /> 7 ), 8 info: ( 9 <Info className="w-4 h-4" /> 10 ), 11 warning: ( 12 <AlertTriangle className="w-4 h-4" /> 13 ), 14 error: ( 15 <XCircle className="w-4 h-4" /> 16 ), 17 loading: ( 18 <Loader2 className="w-4 h-4 animate-spin" /> 19 ), 20 }} 21 style={{ 22 '--normal-bg': 'var(--card)', 23 '--normal-text': 'var(--card-foreground)', 24 '--normal-border': 'var(--border)', 25 '--border-radius': '12px', 26 }} 27 /> 28 ) 29 }
Install Drivn in one command
Copy the source into your project and own every line. Zero runtime dependencies, pure React + Tailwind.
npx drivn@latest createRequires Node 18+. Works with npm, pnpm, and yarn.
Frequently asked questions
Mount it once in your root layout, app/layout.tsx, just before the closing body tag and after {children}. Although the Toaster is a 'use client' component because Sonner manages a portal and queue, a Server Component layout renders it directly, since a server component may render a client child. A single mount serves every route — never render a second Toaster on a page.
No. The toast() function renders into a browser portal, so it must run on the client. A Server Component or Server Action cannot fire a toast directly. The pattern is to run the mutation in the Server Action, then call toast.success or toast.error from the client component that invoked it, branching on the action's return value.
Drivn's Toaster pre-wires five lucide-react icons — success, error, info, warning, and a spinning Loader2 — and themes the toast surface with your --card, --card-foreground, and --border design tokens at a 12px radius. The toast() function is re-exported from Sonner unchanged, so the API is identical while the defaults match the rest of your Drivn UI out of the box.
Yes, automatically. The Toaster's style object maps Sonner's --normal-bg, --normal-text, and --normal-border onto Drivn's --card, --card-foreground, and --border CSS variables. Because those tokens flip with your theme, the toast surface recolors for dark and light mode with no extra prop — recolor the tokens in your theme and every toast follows.

