Feedback
Sonner.
Toast notifications from the sonner package, dressed in Lantern colors with a hard offset shadow and lucide status icons.
"use client";
import { toast } from "sonner";
import { Button } from "@/components/ui/button";
function syncSite() {
return new Promise<{ name: string }>((resolve) => setTimeout(() => resolve({ name: "turtle.farm" }), 2000));
}
export default function SonnerDemo() {
return (
<div className="flex flex-wrap items-center justify-center gap-3">
<Button
variant="outline"
onClick={() => toast("Guestbook signed", { description: "Your note is pinned on guestbook.hub." })}
>
Default
</Button>
<Button variant="outline" onClick={() => toast.success("Site published", { description: "turtle.farm is live." })}>
Success
</Button>
<Button
variant="outline"
onClick={() => toast.error("Server unreachable", { description: "Deepstone stopped answering." })}
>
Error
</Button>
<Button
variant="outline"
onClick={() =>
toast("Turtle T-03 is low on fuel", {
description: "95 moves left.",
action: { label: "Refuel", onClick: () => toast.success("Refuel queued for T-03") },
})
}
>
With action
</Button>
<Button
variant="outline"
onClick={() =>
toast.promise(syncSite(), {
loading: "Syncing site...",
success: (data) => `${data.name} synced`,
error: "Sync failed",
})
}
>
Promise
</Button>
</div>
);
}Installation#
With the CLI
$npx lanterncn add sonnerThis also installs the Lantern theme and any Lantern UI components it depends on. The shadcn CLI works directly too: npx shadcn@latest add httptim/lantern-ui/sonner. See Installation if your project is not set up yet.
Manually
Install the dependencies
npm install sonner lucide-reactCopy the source into your project
components/ui/sonner.tsx"use client"; import * as React from "react"; import { CircleAlert, CircleCheck, Info, Loader2, TriangleAlert, X } from "lucide-react"; import { Toaster as Sonner, type ToasterProps } from "sonner"; /** Lantern-styled toaster. Mount once near the root, then call toast() from anywhere. */ function Toaster({ toastOptions, ...props }: ToasterProps) { return ( <Sonner theme="dark" className="toaster group font-sans!" icons={{ success: <CircleCheck className="size-4 text-success" />, info: <Info className="size-4 text-info" />, warning: <TriangleAlert className="size-4 text-warning" />, error: <CircleAlert className="size-4 text-destructive" />, loading: <Loader2 className="size-4 animate-spin text-primary" />, close: <X className="size-3" />, }} style={ { "--normal-bg": "var(--popover)", "--normal-text": "var(--popover-foreground)", "--normal-border": "var(--border)", "--border-radius": "var(--radius)", } as React.CSSProperties } toastOptions={{ ...toastOptions, classNames: { toast: "font-sans! shadow-block-sm! gap-3! border-border! px-4! py-3.5!", title: "text-sm! font-semibold! text-foreground!", description: "text-[13px]! text-muted-foreground!", icon: "self-start! mt-0.5!", actionButton: "h-7! rounded-sm! bg-primary! px-2.5! font-mono! text-[10px]! font-semibold! tracking-[0.12em]! text-primary-foreground! uppercase!", cancelButton: "h-7! rounded-sm! bg-secondary! px-2.5! font-mono! text-[10px]! tracking-[0.12em]! text-muted-foreground! uppercase!", closeButton: "border-input! bg-popover! text-muted-foreground! hover:text-foreground!", ...toastOptions?.classNames, }, }} {...props} /> ); } export { Toaster };Update the import paths to match your project
The source imports
cnfrom@/lib/utils.
Usage#
import { Toaster } from "@/components/ui/sonner";toast.success("Site published", { description: "turtle.farm is live." })Mount <Toaster /> once in your root layout, then call toast() from any client component. The toaster is always dark, so it does not need next-themes.