Toast
Floating notification plates with an imperative handle — call toast.add anywhere and the mounted Toaster renders it.
@emboss/toast
Installation
pnpm dlx shadcn@latest add @emboss/toastUsage
import { Button } from "@/components/ui/button";
import { toast, Toaster } from "@/components/ui/toast";
export function Example() {
return (
<>
<Toaster />
<Button
onClick={() => {
toast.add({
title: "Preset written",
description: "Slot A updated.",
});
}}
>
Write preset
</Button>
</>
);
}API reference
Toaster
Mount once near the app root; renders the stacked plates in the bottom-right corner.
| Prop | Type | Default | Description |
|---|---|---|---|
| className | string | — | Extends the viewport, e.g. to reposition the stack. |
toast
The imperative manager. toast.add({ title, description, type }) returns an id; toast.close(id) dismisses; toast.promise(promise, { loading, success, error }) tracks an async task.
| Prop | Type | Default | Description |
|---|---|---|---|
| timeout | number | 5000 | Auto-dismiss delay for an added toast, in milliseconds. |
Keyboard
| Keys | Action |
|---|---|
| F6 | Moves focus to the toast viewport. |
Source
View source — toast.tsxHide source — toast.tsx
"use client";
import { Toast as BaseToast } from "@base-ui/react/toast";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";
/**
* The imperative toast handle. Import it anywhere and call
* `toast.add({ title, description })` — the mounted Toaster renders it.
* Also supports `toast.promise(promise, { loading, success, error })`.
*/
const toast = BaseToast.createToastManager();
function ToastList() {
const { toasts } = BaseToast.useToastManager();
return toasts.map((item) => (
<BaseToast.Root
key={item.id}
toast={item}
data-slot="toast"
className="flex w-full items-start gap-3 rounded-md bg-surface-2 p-4 text-ink shadow-float-2 transition-[translate,opacity] duration-(--duration-float) ease-(--ease-float) data-ending-style:translate-y-2 data-ending-style:opacity-0 data-starting-style:translate-y-2 data-starting-style:opacity-0"
>
<BaseToast.Content className="flex min-w-0 flex-1 flex-col gap-0.5">
<BaseToast.Title
data-slot="toast-title"
className="text-sm font-medium"
/>
<BaseToast.Description
data-slot="toast-description"
className="text-sm text-ink-muted"
/>
</BaseToast.Content>
<BaseToast.Close
aria-label="Dismiss notification"
data-slot="toast-close"
className="inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-ink-faint focus-ring transition-[box-shadow,color] hover:text-ink hover:shadow-flush"
>
<X className="size-3.5" aria-hidden />
</BaseToast.Close>
</BaseToast.Root>
));
}
/**
* Mount once near the app root. Floating plates stack in the bottom-right
* corner; swipe and timer behavior come from the underlying primitive.
*
* @example
* <Toaster />
* // elsewhere:
* toast.add({ title: "Preset written", description: "Slot A updated." });
*/
function Toaster({ className }: { className?: string }) {
return (
<BaseToast.Provider toastManager={toast}>
<BaseToast.Portal>
<BaseToast.Viewport
data-slot="toaster"
className={cn(
"fixed right-4 bottom-4 z-50 flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2",
className,
)}
>
<ToastList />
</BaseToast.Viewport>
</BaseToast.Portal>
</BaseToast.Provider>
);
}
export { Toaster, toast };