Skip to content
Notra logoNotra UI
Esc
↑↓navigate↵open⌘Jpreview
On this page

Sonner

A Sonner toaster with the Depth surface of the buttons and tinted status icons.

Toasts get the same surface as the outline button: a vertical gradient, a top highlight, a soft drop shadow and squircle corners. Each status icon sits in a small chip tinted with its colour.

"use client";

import { toast } from "sonner";

import { Button } from "@/components/ui/button";
import { Toaster } from "@/components/ui/sonner";

export default function SonnerTypesExample() {
  return (
    <div className="flex min-h-80 w-full flex-wrap content-start items-center justify-center gap-3 p-6">
      <Button onClick={() => toast("Workspace synced")} variant="outline">
        Default
      </Button>
      <Button
        onClick={() => toast.success("Deployment complete")}
        variant="outline"
      >
        Success
      </Button>
      <Button
        onClick={() =>
          toast.info("New scan scheduled", {
            description: "Runs every Monday at 09:00.",
          })
        }
        variant="outline"
      >
        Info
      </Button>
      <Button
        onClick={() =>
          toast.warning("Seat limit almost reached", {
            description: "9 of 10 seats are in use.",
          })
        }
        variant="outline"
      >
        Warning
      </Button>
      <Button
        onClick={() => toast.error("Something went wrong")}
        variant="outline"
      >
        Error
      </Button>
      <Button
        onClick={() => {
          const id = toast.loading("Publishing changelog...");
          setTimeout(() => toast.success("Changelog published", { id }), 2000);
        }}
        variant="outline"
      >
        Loading
      </Button>
      <Toaster position="bottom-center" />
    </div>
  );
}

Installation

$ bunx --bun shadcn@latest add @notra/notra-sonner

This replaces components/ui/sonner.tsx and pulls in Button for the action buttons. Sonner still does the work underneath, so every toast() call keeps working. Mount the toaster once in your root layout:

import { Toaster } from "@/components/ui/sonner";

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

Status colours

Success, warning and info read your --success, --warning and --info tokens. If your theme doesn’t define them, the toaster falls back to Notra’s colours. Errors use --destructive.

Actions

"use client";

import { toast } from "sonner";

import { Button } from "@/components/ui/button";
import { Toaster } from "@/components/ui/sonner";

export default function SonnerActionExample() {
  return (
    <div className="flex min-h-64 w-full flex-wrap content-start items-center justify-center gap-3 p-6">
      <Button
        onClick={() =>
          toast("Post moved to trash", {
            action: {
              label: "Undo",
              onClick: () => toast.success("Post restored"),
            },
          })
        }
        variant="outline"
      >
        With action
      </Button>
      <Button
        onClick={() =>
          toast.error("Couldn't change role", {
            cancel: { label: "Dismiss", onClick: () => undefined },
            description: "Your plan includes 3 team members.",
          })
        }
        variant="outline"
      >
        With cancel
      </Button>
      <Toaster position="bottom-center" />
    </div>
  );
}

Action buttons render with Button at size xs, and cancel buttons use the ghost variant. The CLI installs Button along with the toaster, so they get the Depth look too.

toast("Post moved to trash", {
  action: {
    label: "Undo",
    onClick: () => restorePost(),
  },
});