abnormal.UI overview

Overlays

Toaster

Toast notifications on sonner, following the next-themes theme.

Preview

Install

pnpm dlx shadcn@latest add @abnormal/sonner
Brings inutilslucide-reactnext-themessonner

Usage

"use client";

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

export default function SonnerDemo() {
return (
<div className="flex flex-wrap justify-center gap-2">
<Toaster />
<Button variant="outline" onClick={() => toast("Draft saved")}>
Default
</Button>
<Button variant="outline" onClick={() => toast.success("Message sent")}>
Success
</Button>
<Button variant="outline" onClick={() => toast.error("Could not send")}>
Error
</Button>
</div>
);
}
Source
"use client";

import { useTheme } from "next-themes";
import { Toaster as Sonner, type ToasterProps } from "sonner";
import { CircleCheckIcon, InfoIcon, TriangleAlertIcon, OctagonXIcon, Loader2Icon } from "lucide-react";

/** Toasts sit on a popover surface; success and failure speak only through the icon color, without tinting the whole block. */
const Toaster = ({ ...props }: ToasterProps) => {
const { theme = "system" } = useTheme();

return (
<Sonner
theme={theme as ToasterProps["theme"]}
className="toaster group"
icons={{
success: <CircleCheckIcon className="size-4 text-success" />,
info: <InfoIcon className="size-4" />,
warning: <TriangleAlertIcon className="size-4" />,
error: <OctagonXIcon className="size-4 text-destructive" />,
loading: <Loader2Icon className="size-4 animate-spin" />,
}}
style={
{
"--normal-bg": "var(--popover-bg)",
"--normal-text": "var(--text)",
"--normal-border": "var(--border)",
"--border-radius": "var(--radius-panel)",
} as React.CSSProperties
}
toastOptions={{
classNames: {
toast: "cn-toast",
},
}}
{...props}
/>
);
};

export { Toaster };