Sonner

An opinionated toast component for React.

import { toast } from 'sonner'
import { Button } from 'ui'
 
export function SonnerDemo() {
  return (
    <Button
      variant="default"
      onClick={() =>
        toast('Event has been created', {
          description: 'Sunday, December 03, 2023 at 9:00 AM',
          cancel: {
            label: 'Undo',
            onClick: () => console.log('Undo'),
          },
        })
      }
    >
      Show Toast
    </Button>
  )
}

About

Sonner is built and maintained by emilkowalski_.

Installation

Install the following dependencies:

npm install sonner next-themes

Copy and paste the following code into your project.

'use client'
 
import { Toaster as Sonner } from 'sonner'
 
import { cn } from '../../../lib/utils'
import { buttonVariants } from '../../Button'
import { StatusIcon } from './../../StatusIcon'
 
export const SONNER_DEFAULT_DURATION = 4000
 
type ToasterProps = React.ComponentProps<typeof Sonner> & { theme: 'light' | 'dark' | 'system' }
 
const SonnerToaster = ({ toastOptions, ...props }: ToasterProps) => {
  return (
    <Sonner
      icons={{
        warning: <StatusIcon variant="warning" />,
        error: <StatusIcon variant="destructive" />,
        info: <StatusIcon variant="default" />,
      }}
      // pointer-events-auto is needed to fix the toast when above radix modals. Set the width to 420px to fix the toast
      // progress component (bottom row rendered in two lines).
      className="toaster group pointer-events-auto"
      // fontFamily: 'inherit' is needed to use the same font as the rest of the app
      style={{ fontFamily: 'inherit' }}
      toastOptions={{
        unstyled: true,
        classNames: {
          toast: cn(
            'group',
            'toast',
            'w-full',
            'rounded-md',
            'py-3',
            'px-5',
            'flex',
            'gap-2',
            'items-start',
            'font-normal',
            'text-sm',
            'data-[expanded=false]:overflow-hidden',
            'group-[.toaster]:bg-overlay group-[.toaster]:text-foreground group-[.toaster]:border group-[.toaster]:border-overlay group-[.toaster]:shadow-lg'
          ),
          icon: 'mt-0.5',
          title: 'font-normal!',
          description:
            'text-xs group-[.toast]:text-foreground-lighter transition-opacity group-data-[expanded=false]:opacity-0 group-data-[front=true]:opacity-100!',
          actionButton: cn('block', buttonVariants({ variant: 'primary', size: 'tiny' })),
          cancelButton: cn('block', buttonVariants({ variant: 'default', size: 'tiny' })),
          // success: 'group toast group-[.toaster]:!bg-brand-200 group-[.toaster]:border-brand-500',
          warning:
            'group toast group-[.toaster]:!bg-warning-200 group-[.toaster]:!border-warning-500',
          error:
            'group toast group-[.toaster]:!bg-destructive-200 group-[.toaster]:!border-destructive-500',
          closeButton: cn(
            // unset all styles set from sonner
            'absolute right-2 top-2 size-6 flex items-center justify-center rounded-md text-foreground-light opacity-0 transition-opacity',
            'hover:text-foreground hover:bg-surface-200 focus-visible:opacity-100 focus-ring group-hover:opacity-100',
            'group-[.destructive]:text-destructive-300 group-[.destructive]:hover:text-destructive-50',
            'group-[.destructive]:focus-visible:ring-destructive-400 group-[.destructive]:focus-visible:ring-offset-destructive-600',
            'left-auto transform-none border-0 border-transparent'
          ),
          content: 'grow',
          //group-[.toaster]:bg-overlay group-[.toaster]:text-foreground group-[.toaster]:border group-[.toaster]:border-overlay
        },
        duration: SONNER_DEFAULT_DURATION,
        closeButton: true,
        ...toastOptions,
      }}
      cn={cn}
      {...props}
    />
  )
}
 
export { SonnerToaster }

Add the Toaster component

app/layout.tsx
import { Toaster } from '@/components/ui/sonner'
 
export default function RootLayout({ children }) {
  return (
    <html lang="en">
      <head />
      <body>
        <main>{children}</main>
        <Toaster />
      </body>
    </html>
  )
}

Usage

import { toast } from 'sonner'
toast('Event has been created.')

When to use

Use a toast for short-lived, non-blocking feedback or to confirm an operation after its originating surface has closed or navigated away.

Do not use a toast as the only feedback for:

  • Field validation. Use FormMessage or FieldError beside the field.
  • A failed submission that leaves the form or interstitial visible. Show a role="alert" message in destructive text near the actions.
  • A blocking or materially changed page state. Use an inline state component such as Admonition or ErrorDisplay.

Keep error copy specific and include the next step when it is not obvious.

Expand

You can change the amount of toasts visible through the visibleToasts prop.

<Toaster expand={true | false} />

You can use the Input below to try it out.

You will need to fire a few Sonner toasts first

Position

You can change the amount of toasts visible through the visibleToasts prop.

<Toaster
  position={
    'top-left' | 'top-right' | 'bottom-left' | 'bottom-right' | 'top-center' | 'bottom-center'
  }
/>

You can use the Input below to try it out.

You will need to fire a few Sonner toasts first

Types

You can customize the type of toast you want to render, and pass an options object as the second argument.

Loading...

Upload example

Loading...