Skip to main content

sonner

An opinionated toast component for React.

About

Sonner is built and maintained by emilkowalski_.

Philosophy

Toasts are the UI equivalent of a tap on the shoulder — noticeable but not blocking. We wrap Sonner because it handles the hard parts (stacking, dismissal timing, swipe gestures, accessibility announcements) better than a from-scratch implementation. Our layer adds visual consistency with the design system and upload progress integration.

How It's Built

Loading diagram...

Installation

Run the following command:


npx @gentleduck/cli add sonner

npx @gentleduck/cli add sonner

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>
  )
}
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'
import { toast } from 'sonner'
toast('Event has been created.')
toast('Event has been created.')

Examples

Default

Upload

Toast Types

Multi-File Upload

API Reference

Toaster

The Toaster component wraps the Sonner Toaster from the sonner library, applying theme synchronization via next-themes and default styling.

PropTypeDefaultDescription
dir'ltr' | 'rtl'--Text direction override. Resolved via useDirection (dir prop -> DirectionProvider -> 'ltr').
theme'light' | 'dark' | 'system''system'Toast theme. Auto-detected from next-themes useTheme()
...propsToasterProps--Additional props inherited from the sonner toaster.

SonnerUpload

PropTypeDefaultDescription
progressnumber(required)Current upload progress percentage (0--100)
attachmentsnumber(required)Number of files being uploaded
remainingTimenumber--Estimated remaining upload time in seconds
onCancel(e: React.MouseEvent<HTMLButtonElement>, dismiss: (id: string) => void) => void--Callback triggered when the Cancel button is clicked. Receives the click event and a function to dismiss the toast by ID
onComplete(e: React.MouseEvent<HTMLButtonElement>, dismiss: (id: string) => void) => void--Callback triggered when the Complete button is clicked. Receives the click event and a function to dismiss the toast by ID

SonnerUpload shows a spinning Loader icon while progress < 100 and switches to a success CircleCheck icon at completion.

Utility Function

formatTime(seconds: number): string

Formats a duration in seconds into a human-readable string:

  • Returns days if >= 1 day ('Xd '),
  • else hours if >= 1 hour ('Xh '),
  • else minutes if >= 1 minute ('Xm '),
  • else seconds ('Xs').

Types

export type UploadSonnerProps = {
  progress: number
  attachments: number
  remainingTime?: number
  onCancel?: (
    e: React.MouseEvent<HTMLButtonElement>,
    dismiss: (id: string) => void
  ) => void
  onComplete?: (
    e: React.MouseEvent<HTMLButtonElement>,
    dismiss: (id: string) => void
  ) => void
}
 
export type ToasterProps = React.ComponentProps<typeof Sonner>
export type UploadSonnerProps = {
  progress: number
  attachments: number
  remainingTime?: number
  onCancel?: (
    e: React.MouseEvent<HTMLButtonElement>,
    dismiss: (id: string) => void
  ) => void
  onComplete?: (
    e: React.MouseEvent<HTMLButtonElement>,
    dismiss: (id: string) => void
  ) => void
}
 
export type ToasterProps = React.ComponentProps<typeof Sonner>

RTL Support

Direction is resolved through the shared primitives direction module. Use a local dir="rtl" override when the component exposes it, or set DirectionProvider at app/root level for global RTL/LTR behavior.