Sonner
Toast notifications on Sonner that follow Ward's light and dark theme, with Toaster and toast imported from one place.
"use client"
/*
* Adapted from shadcn/ui (https://github.com/shadcn-ui/ui).
*
* MIT License
*
* Copyright (c) 2023 shadcn
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
import * as React from "react"
import { Toaster as Sonner, type ToasterProps } from "sonner"
// Apps import toast from here too, so the Toaster and every toast() call share one sonner instance.
export { toast } from "sonner"
// Ward sets data-theme on <html>; shadcn projects toggle the .dark class. Read either instead of next-themes.
function useDocumentTheme(): ToasterProps["theme"] {
const [theme, setTheme] = React.useState<ToasterProps["theme"]>("light")
React.useEffect(() => {
const root = document.documentElement
const read = () =>
setTheme(
root.dataset.theme === "dark" || root.classList.contains("dark")
? "dark"
: "light"
)
read()
const observer = new MutationObserver(read)
observer.observe(root, {
attributes: true,
attributeFilter: ["class", "data-theme"],
})
return () => observer.disconnect()
}, [])
return theme
}
const Toaster = ({ ...props }: ToasterProps) => {
const theme = useDocumentTheme()
return (
<Sonner
theme={theme}
className="toaster group"
style={
{
"--normal-bg": "var(--popover)",
"--normal-text": "var(--popover-foreground)",
"--normal-border": "var(--border)",
} as React.CSSProperties
}
{...props}
/>
)
}
export { Toaster }Installation
pnpm dlx shadcn@latest add @ward/sonnernpx shadcn@latest add @ward/sonneryarn dlx shadcn@latest add @ward/sonnerbunx --bun shadcn@latest add @ward/sonnerAdd the Ward registry to components.json once:
"registries": {
"@ward": "https://ward.so/r/{name}.json"
}Or install from the URL with no configuration: npx shadcn@latest add https://ward.so/r/sonner.json
Install the dependencies.
npm install sonner@2.0.8Copy each file from the Code tab into:
components/ui/sonner.tsx
Update the import paths to match your project.
Usage
import { Toaster, toast } from "@/components/ui/sonner";
import { Button } from "@/components/ui/button";// Once, in your root layout
<Toaster />
// Anywhere in a client component
<Button onClick={() => toast("Saved")}>Save</Button>
<Button variant="outline" onClick={() => toast.success("Link copied", { description: "Paste it anywhere." })}>Copy link</Button>Props
| Prop | Type | Default | Description |
|---|---|---|---|
toast(message, options) | (message: ReactNode, options?: ExternalToast) => string | number | - | Show a toast and return its id. Import it from this file, not from sonner, so it reaches the same Toaster. |
toast.success, error, info, warning, loading | (message, options?) => string | number | - | The same call with a matching icon; loading stays until you update or dismiss it. |
toast.promise | (promise, { loading, success, error }) => ... | - | Show a loading toast that turns into a success or error toast when the promise settles. |
toast.dismiss | (id?: string | number) => void | - | Close one toast by id, or every toast when no id is given. |
options description | ReactNode | - | Second line of smaller text under the message. |
options action | { label: string; onClick: () => void } | - | Button in the toast, such as Undo. |
options duration | number | 4000 | Milliseconds before the toast closes; Infinity keeps it until it is dismissed. |
Toaster position | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | "bottom-right" | Corner or edge of the screen the toasts stack in. |
Toaster closeButton | boolean | false | Show a close button on every toast. |
Toaster theme | "light" | "dark" | "system" | - | Follows the data-theme attribute or dark class on the html element; set it only to force one. |
Ward · Version 1.0.0 · Registry manifest