Skip to content
Feedback

Sonner — Component Library Guide

An opinionated toast component for React. Beautiful by default, stacking animations, types (success, error, warning), and rich content support.

TypeScript MIT
2.1M weekly downloads
9.4k GitHub stars
~4.4kB bundle size
v1.7.4 latest version
toastnotificationsanimationsaccessibleminimal

TL;DR: Sonner is an opinionated toast library with polished default styles. One <Toaster /> in your app root, then call toast() from anywhere - no hooks, no context. Handles stacking, hover-to-expand, and swipe-to-dismiss. ~4.4kB. Official docs: sonner.emilkowal.ski.

What is Sonner?

Sonner is a toast notification library with an extremely minimal API and polished default styles. You render one <Toaster /> in your app, then call toast() from anywhere - no context, no hooks, no state management required. It handles stacking, hover-to-expand, and swipe-to-dismiss out of the box.

I switched a form-heavy admin panel from a hand-rolled toast context (about 90 lines of provider, reducer, and portal code) to Sonner in under half an hour, and deleted all of it the same afternoon. The stacking behavior alone was worth the swap - our old implementation just piled new toasts on top of old ones until the corner of the screen turned into a wall of unreadable text.

When to use it

Use Sonner when you want great-looking toasts with zero configuration. It's the default toast library in shadcn/ui and handles 90% of use cases elegantly.

Sonner vs React Hot Toast: Sonner has better default animations and stacking behaviour. React Hot Toast is slightly smaller and has been around longer. Both are excellent - Sonner has more momentum in 2024 - 2025.

Key Features

  • Stack animation - multiple toasts stack with a fan effect; hover to expand
  • Toast types - toast.success(), toast.error(), toast.warning(), toast.info()
  • Promise toasts - toast.promise() auto-transitions through loading → success/error
  • Rich content - render custom JSX inside a toast
  • Position - 6 positions including top-center and bottom-right

Installation

npm install sonner

Basic Setup

// app/layout.tsx or root component
import { Toaster } from 'sonner'

export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html>
      <body>
        {children}
        <Toaster position="bottom-right" richColors />
      </body>
    </html>
  )
}

Usage

import { toast } from 'sonner'

function SaveButton() {
  async function handleSave() {
    toast.promise(saveData(), {
      loading: 'Saving...',
      success: 'Changes saved',
      error: 'Failed to save',
    })
  }

  return <button onClick={handleSave}>Save</button>
}

// Inline usage anywhere:
toast.success('Profile updated')
toast.error('Something went wrong', { description: 'Please try again later.' })
toast.warning('Unsaved changes', { action: { label: 'Save now', onClick: save } })

Custom Toast with Action

toast('File deleted', {
  description: 'document.pdf has been removed',
  action: {
    label: 'Undo',
    onClick: () => restoreFile(),
  },
  duration: 5000,
})

TypeScript Tips

Sonner is fully typed. The toast() return value is the toast ID - use it to dismiss or update a toast programmatically:

const id = toast.loading('Processing...')

try {
  await processData()
  toast.success('Done!', { id })  // replaces the loading toast
} catch {
  toast.error('Failed', { id })
}

Limiting How Many Toasts Stack at Once

By default Sonner shows 3 stacked toasts before collapsing the rest into the pile. On a page that fires a lot of background notifications (bulk import progress, websocket events), that default can still feel noisy. visibleToasts and a shared max-count guard keep it in check:

<Toaster visibleToasts={2} />
import { toast } from 'sonner'

let activeImportToastId: string | number | undefined

function notifyImportProgress(done: number, total: number) {
  const message = `Imported ${done} of ${total} rows`
  activeImportToastId = toast.loading(message, { id: activeImportToastId })
}

Reusing the same id on every call replaces the existing toast in place instead of stacking a new one for every progress tick, which matters a lot once an import fires updates every few hundred milliseconds.

Common Gotchas

  • One <Toaster /> per app - render it once at the root; multiple Toaster instances conflict
  • richColors prop - add this to <Toaster> to get coloured backgrounds for success/error/warning toasts instead of monochrome
  • Duration - default duration is 4000ms. Override globally on <Toaster duration={6000}> or per-toast with toast('msg', { duration: Infinity })
  • Server components - toast() is a client-side call; call it from event handlers or client components, not server actions directly
  • React Hot Toast - the alternative toast library; slightly older, fully customisable render function, similar API
  • React 19 Guide - call toast() after awaiting a React 19 Action to surface server mutation results in the UI

Server Actions Integration (Next.js)

toast() is a client-side call. The recommended pattern with React 19 Actions or Next.js Server Actions is to call it in the client after the action resolves:

'use client'
import { toast } from 'sonner'
import { updateProfile } from '@/actions/profile'

function ProfileForm() {
  async function handleSubmit(formData: FormData) {
    const result = await updateProfile(formData)
    if (result.error) {
      toast.error(result.error)
    } else {
      toast.success('Profile updated')
    }
  }

  return <form action={handleSubmit}>...</form>
}

Theming and Dark Mode

Sonner respects the theme prop and automatically adapts to dark mode:

<Toaster theme="system" /> {/* auto: light/dark based on prefers-color-scheme */}
<Toaster theme="dark" />
<Toaster theme="light" />

Pass richColors together with theme for coloured success/error/warning backgrounds that stay readable in both modes. The stacking limit (default 3 visible toasts) can be changed via <Toaster visibleToasts={5} />. Pass a custom icon per toast call to override the default success/error icons.

Frequently Asked Questions

What is the difference between Sonner and React Hot Toast?

Sonner has more polished stacking animations - multiple toasts fan out and expand on hover for a native app feel. React Hot Toast is slightly older with a larger existing install base. Both are excellent. Sonner is the newer choice and the default in shadcn/ui.

How do I add coloured success and error toasts in Sonner?

Add the richColors prop to the Toaster component. This gives success toasts a green background, error toasts a red background, and warning toasts an amber background instead of the default neutral styling.

Can I display a toast from a Server Action in Next.js?

toast() is a client-side call and cannot run in a Server Action directly. Call it in the client component that invokes the server action, inside the action's result handler after the await resolves.

How do I show a loading toast that updates to success or error?

Use toast.promise() which accepts a promise and three message strings for loading, success, and error states. Alternatively, call toast.loading() to get a toast ID, then pass that ID to toast.success() or toast.error() to replace the loading toast.

How do I dismiss all toasts programmatically?

Call toast.dismiss() without arguments to dismiss all active toasts. To dismiss a specific toast, store the ID returned by the toast() call and pass it to toast.dismiss(id).