Examples
Basic notification
A short, non-blocking message confirms a completed action.
Toast provider, viewport, and add action
"use client";
import { Button } from "@phuctech/ui/components/button";
import { ToastClose, ToastContent, ToastDescription, ToastRoot, ToastTitle, ToastProvider, ToastViewport, useToastManager } from "@phuctech/ui/components/toast";
function ToastList() {
const { toasts } = useToastManager();
return toasts.map((toast) => <ToastRoot key={toast.id} toast={toast}><ToastContent><div className="min-w-0 flex-1"><ToastTitle /><ToastDescription /></div><ToastClose aria-label="Dismiss notification" /></ToastContent></ToastRoot>);
}
function AddToast() {
const manager = useToastManager();
return <Button onClick={() => manager.add({ title: "Changes saved", description: "Your workspace is up to date." })}>Save changes</Button>;
}
export function Example() {
return <ToastProvider><AddToast /><ToastViewport><ToastList /></ToastViewport></ToastProvider>;
}
Semantic types
Success, information, warning, and error use a readable title and description.
Toast semantic types
"use client";
import { Button } from "@phuctech/ui/components/button";
import { ToastClose, ToastContent, ToastDescription, ToastIcon, ToastRoot, ToastTitle, ToastProvider, ToastViewport, useToastManager } from "@phuctech/ui/components/toast";
function ToastList() {
const { toasts } = useToastManager();
return toasts.map((toast) => <ToastRoot key={toast.id} toast={toast}><ToastContent><ToastIcon type={toast.type} /><div className="min-w-0 flex-1"><ToastTitle /><ToastDescription /></div><ToastClose aria-label="Dismiss notification" /></ToastContent></ToastRoot>);
}
function TypeButtons() {
const manager = useToastManager();
return <div className="flex flex-wrap gap-2">{(["success", "info", "warning", "error"] as const).map((type) => <Button key={type} size="sm" variant="outline" onClick={() => manager.add({ type, title: `${type[0]!.toUpperCase()}${type.slice(1)} update`, description: "The notification carries a readable semantic label." })}>{type}</Button>)}</div>;
}
export function Example() {
return <ToastProvider><TypeButtons /><ToastViewport><ToastList /></ToastViewport></ToastProvider>;
}
Update by ID
A pending operation can update to success or error without adding another toast.
Toast that updates from pending to success
"use client";
import { useState } from "react";
import { Button } from "@phuctech/ui/components/button";
import { ToastClose, ToastContent, ToastDescription, ToastRoot, ToastTitle, ToastProvider, ToastViewport, useToastManager } from "@phuctech/ui/components/toast";
function ToastList() {
const { toasts } = useToastManager();
return toasts.map((toast) => <ToastRoot key={toast.id} toast={toast}><ToastContent><div className="min-w-0 flex-1"><ToastTitle /><ToastDescription /></div><ToastClose aria-label="Dismiss notification" /></ToastContent></ToastRoot>);
}
function UpdateButton() {
const [busy, setBusy] = useState(false);
const manager = useToastManager();
return <Button loading={busy} onClick={() => { setBusy(true); const id = manager.add({ id: "sync", type: "info", title: "Syncing", description: "The operation is in progress.", timeout: 0 }); window.setTimeout(() => { setBusy(false); manager.update(id, { type: "success", title: "Sync complete", description: "The same toast was updated." }); }, 500); }}>Sync workspace</Button>;
}
export function Example() {
return <ToastProvider><UpdateButton /><ToastViewport><ToastList /></ToastViewport></ToastProvider>;
}
Action and manual close
Optional actions and an explicit close control remain keyboard reachable.
Toast with action and close
"use client";
import { Button } from "@phuctech/ui/components/button";
import { ToastAction, ToastClose, ToastContent, ToastDescription, ToastRoot, ToastTitle, ToastProvider, ToastViewport, useToastManager } from "@phuctech/ui/components/toast";
function ToastList() {
const { toasts } = useToastManager();
return toasts.map((toast) => <ToastRoot key={toast.id} toast={toast}><ToastContent><div className="min-w-0 flex-1"><ToastTitle /><ToastDescription /><ToastAction onClick={() => undefined}>Undo</ToastAction></div><ToastClose aria-label="Dismiss notification" /></ToastContent></ToastRoot>);
}
function AddToast() {
const manager = useToastManager();
return <Button onClick={() => manager.add({ title: "Project archived", description: "You can undo this action for a short time." })}>Archive project</Button>;
}
export function Example() {
return <ToastProvider><AddToast /><ToastViewport><ToastList /></ToastViewport></ToastProvider>;
}
Limited stack
The provider limit prevents a burst of notifications from taking over the viewport.
Toast provider with a visible limit
"use client";
import { Button } from "@phuctech/ui/components/button";
import { ToastClose, ToastContent, ToastDescription, ToastRoot, ToastTitle, ToastProvider, ToastViewport, useToastManager } from "@phuctech/ui/components/toast";
function ToastList() {
const { toasts } = useToastManager();
return toasts.map((toast) => <ToastRoot key={toast.id} toast={toast}><ToastContent><div className="min-w-0 flex-1"><ToastTitle /><ToastDescription /></div><ToastClose aria-label="Dismiss notification" /></ToastContent></ToastRoot>);
}
function AddBurst() {
const manager = useToastManager();
return <Button onClick={() => { for (let index = 1; index <= 4; index += 1) manager.add({ title: `Notification ${index}`, description: "The provider keeps the visible stack bounded." }); }}>Create burst</Button>;
}
export function Example() {
return <ToastProvider limit={3}><AddBurst /><ToastViewport><ToastList /></ToastViewport></ToastProvider>;
}
When to use
Use Toast to confirm a background operation or provide a short status update without blocking the current task. Use Dialog for confirmation and visible inline feedback for errors that must be acted on before continuing.
Usage
"use client";
import { Button } from "@phuctech/ui/components/button";
import { ToastClose, ToastContent, ToastDescription, ToastRoot, ToastTitle, ToastProvider, ToastViewport, useToastManager } from "@phuctech/ui/components/toast";
function ToastList() {
const { toasts } = useToastManager();
return toasts.map((toast) => <ToastRoot key={toast.id} toast={toast}><ToastContent><div className="min-w-0 flex-1"><ToastTitle /><ToastDescription /></div><ToastClose aria-label="Dismiss notification" /></ToastContent></ToastRoot>);
}
function AddToast() {
const manager = useToastManager();
return <Button onClick={() => manager.add({ title: "Changes saved", description: "Your workspace is up to date." })}>Save changes</Button>;
}
export function Example() {
return <ToastProvider><AddToast /><ToastViewport><ToastList /></ToastViewport></ToastProvider>;
}
Wrap each notification surface in ToastProvider, render one ToastViewport, and call useToastManager from a descendant. The provider owns timeout and visible-stack limits.
Lifecycle
- Use
manager.addfor a new message; an existingidupdates that toast in place. - Use
timeout=0for work that stays visible until it is updated or closed. - Use
manager.updatefor pending → success/error transitions andmanager.closefor an imperative dismissal. - Keep destructive confirmation in Dialog; Toast is not a confirmation control.
API
Tokens
Accessibility
- Toast titles and descriptions are announced by the Base UI manager; keep them meaningful without color.
- Provide a close control for persistent or long messages and make actions keyboard reachable.
- Do not put required instructions, form errors, or destructive confirmations only in a transient Toast.
Related
- Dialog handles blocking confirmation and focused work.
- Settings shows inline save-state feedback around a form.
- Accessibility documents live feedback review.