Toaster is a thin wrapper around Sonner wired
up with the product’s theme tokens. It ships as two pieces that work together:
<Toaster /> is a component you mount once near the root of the app, and
toast(...) (plus toast.success, toast.error, toast.info,
toast.warning, toast.loading, toast.promise) is a function you import
from @unkey/ui and call from anywhere — event handlers, data hooks,
server-action responses — to make a toast appear in that single mounted region.
Reach for a toast when feedback is ephemeral and non-blocking: “saved”,
“copied”, “deleted” (with an undo), “could not reach the server”. Use an
<Alert> instead when the message is inline and persistent (a deprecated-API
banner, a read-only-mode notice). Use a <Dialog> when the user must
acknowledge or confirm before continuing. Toasts auto-dismiss, overlap other
content, and can be missed, so they should never be the only record of a
critical event.
Mounting
Mount <Toaster /> exactly once, as high in the tree as makes sense (the
root layout or a top-level provider). Every toast(...) call on the page
targets this single instance, and mounting it twice causes toasts to appear in
both regions.
import { Toaster } from "@unkey/ui";
export function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang="en">
<body>
{children}
<Toaster position="bottom-right" richColors />
</body>
</html>
);
}
The design system site does not mount a global <Toaster />, so each
example on this page mounts its own local <Toaster /> inside
_examples.tsx alongside the trigger button. That is only a documentation
workaround — in a real app, mount it once.
Import
import { Toaster, toast } from "@unkey/ui";
Toaster is the component you render. toast is the imperative function
(with .success, .error, .info, .warning, .loading, .promise,
.dismiss, .message, .custom attached) you call to show a message.
Default toast
Call toast(message) for a neutral notification. No icon, no colored border
— use this for informational confirmations that don’t need semantic weight.
Success
toast.success(message) adds a success icon and (when richColors is on)
green theming. Use it for confirmations of positive user actions: a key
rotated, an invitation sent, a configuration saved.
Error
toast.error(message, options) flags a failure. Pair with a description
that tells the user what to do next. Error toasts auto-dismiss just like
any other; if the user needs to read and act on the failure, consider an
<Alert> in the affected surface instead.
With description
Pass a description in the options object for a second line of body copy.
Keep the title short (the “what”); put supporting detail in the description
(the “who”, “where”, or “when”).
With action
Pass an action with a label and an onClick to put an inline button
inside the toast. The classic use is an undo affordance after a destructive
operation: the destructive work runs optimistically, and the toast gives the
user a short window to reverse it before dismissal.
Accessibility
Sonner renders toasts into an aria-live region (polite by default), so
screen readers announce new toasts without interrupting the current
announcement. The region also carries a configurable label
(containerAriaLabel, default "Notifications") so assistive tech can
name it when a user jumps to landmarks.
Two rules keep toast usage honest. First, because toasts auto-dismiss and
can be missed entirely (a user on another tab, a screen reader user who
stepped away), never let a toast be the only record of a failure that
blocks further work — duplicate the state in the affected UI. Second, the
hotkey (default Alt+T) focuses the toast region so keyboard users can
reach any action button inside; pressing Escape from there dismisses the
focused toast.
Props
<Toaster>
position "top-left" | "top-right" | "bottom-left" | "bottom-right" | "top-center" | "bottom-center" Optional
Default "bottom-right" Which corner of the viewport the stack anchors to. Bottom-right is the dashboard default; top-center is common for marketing or onboarding flows where the user’s eye is already near the top.
richColors boolean Optional
Default false When true, success, error, warning, and info toasts render on
semantically tinted backgrounds. Off by default so toasts stay visually
quiet; turn on per-app if you want the extra signal.
expand boolean Optional
Default false When true, stacked toasts are all fully visible instead of collapsing
behind the topmost one. Useful when multiple notifications fire in
rapid succession and the user should see all of them.
duration number Optional
Default 4000 Default milliseconds before a toast auto-dismisses. Override per toast
by passing duration in the toast(...) options. Use Infinity to
require an explicit dismiss (pair with closeButton).
visibleToasts number Optional
Default 3 How many toasts are visible at once; additional toasts queue and surface as older ones dismiss.
closeButton boolean Optional
Default false When true, every toast renders a close (x) affordance. Recommended
when duration is very long or Infinity.
theme "light" | "dark" | "system" Optional
Default "system" Follows next-themes automatically; override only when you need to
pin a specific theme for a subtree.
hotkey string[] Optional
Default ["altKey", "KeyT"] Key combination that focuses the toast region so keyboard users can
reach action buttons. Provide as an array of KeyboardEvent property
names.
offset string | number | Offset Optional Pixel offset from the chosen position. Pass a single value to apply
to both axes, or an object like { top, right, bottom, left } to
target individual edges.
toastOptions ToastOptions Optional Default options merged into every toast. Our wrapper already wires the
product’s bg-background, text-content, and border-border tokens
here; override sparingly.
containerAriaLabel string Optional
Default "Notifications" Accessible name for the live region.
toast(...)
The imperative entry point. Every variant returns a toast id you can
pass to toast.dismiss(id) to dismiss early.
toast(message, options?)
toast.success(message, options?)
toast.error(message, options?)
toast.info(message, options?)
toast.warning(message, options?)
toast.loading(message, options?)
toast.message(message, options?)
toast.promise(promise, { loading, success, error })
toast.custom((id) => <MyToast id={id} />, options?)
toast.dismiss(id?)
message ReactNode Optional The title of the toast. Keep it short — one line, active voice
(“Saved”, “Key rotated”, “Invite sent”). Detail goes in
description.
options.description ReactNode Optional A second line of body copy rendered beneath the title in muted color.
options.action { label: ReactNode; onClick: (event) => void } Optional An inline button inside the toast. Classic use is undo after a
destructive action; the onClick receives the mouse event and can
call toast.dismiss(id) if you want to close immediately on click.
options.cancel { label: ReactNode; onClick: (event) => void } Optional A secondary inline button styled as a cancel affordance.
options.duration number Optional Per-toast override of the Toaster’s default duration, in
milliseconds. Infinity keeps the toast until dismissed.
options.id string | number Optional Stable id for this toast. Calling toast(..., { id }) again with the
same id updates the existing toast in place instead of stacking — use
this to reflect progress (loading then success on the same id).
options.dismissible boolean Optional
Default true When false, the toast ignores swipe and close-button dismissal, and
stays until its duration elapses (or forever, if Infinity).
options.onDismiss (toast) => void Optional Fires when the user dismisses the toast manually (swipe or close button).
options.onAutoClose (toast) => void Optional Fires when the toast closes on its own after duration elapses.
Related
- Alert — inline, persistent region for context that must stay visible while the condition holds.
- Dialog — blocking confirmation when the user must acknowledge before continuing.