# Toast

> A short note in the corner after something happened. An undo toast offers Undo for 8 seconds, then commits the change. Tones say how it went, and a promise toast follows work to its end.

- Name: `toast`
- Status: beta
- Categories: feedback
- File: the install writes `toast.tsx` to your `ui` alias, `@/components/ui` by default.
- npm packages: `@base-ui/react`, `class-variance-authority`. The install adds them.
- Other registry items: `@txbrendan/button`. The install adds them.
- Gallery: https://txbrendan.dev/components/toast
- Registry item: https://txbrendan.dev/r/toast.json

## Install

1. Add the registry to `components.json`. Do this first, once:

   ```bash
   npx shadcn@latest registry add "@txbrendan=https://txbrendan.dev/r/{name}.json"
   ```

   The command writes this entry. You can also add it by hand:

   ```json
   {
     "registries": {
       "@txbrendan": "https://txbrendan.dev/r/{name}.json"
     }
   }
   ```

   Without this step, the shadcn CLI looks up `@txbrendan` in a public list that txbrendan/ui does not control.

2. Add the theme once per app. It replaces your color tokens with ones checked for contrast:

   ```bash
   npx shadcn@latest add @txbrendan/theme
   ```

3. Add the component:

   ```bash
   npx shadcn@latest add @txbrendan/toast
   ```

## Use it for

- Place one \<Toaster /> near the end of the root layout. Then call toast() from any client code, like an event handler.
- Apply a change that can be undone at once, then call toast.undoable() with onUndo to put it back. onCommit runs when the 8 seconds end.
- Use toast.promise() for work that lands somewhere else, like an import or a send. It shows a spinner, then success or danger.
- Use toast() for news that nothing on screen shows, like a reminder that went out. Give it one action that goes to where the news lives.
- Say what happened in the title, in a few words, like 'Archived Design review'. Add a description only when the title needs it.
- Use tone 'danger' and timeout: 0 for a failure people must see, like a send that did not go, and give it a Retry action.

## Avoid it for

- Asking before an action that can be undone. Do it, and offer Undo in a toast.
- An action that cannot be undone, like a payment or a removal. Use Alert Dialog.
- A problem with one field or row. Say it under the thing with Notice.
- A success toast for what the screen already shows, like a saved field. Show the result in place.
- More than one Toaster on a page. Each one draws every toast, so each would show twice.
- Rendering \<Toast> to pop a toast. It is a still picture for docs. Call toast().

## Props

- `Toaster`: `{ limit?, timeout?, className?, aria-label? }`. Where toasts show, in the bottom right corner. Place one on the page.
- `limit`: `number`. Default `1`. Toaster. How many toasts show at once. A newer one replaces the oldest.
- `timeout`: `number`. Default `5000`. How long a toast stays, in milliseconds: on Toaster for every toast, or in toast() for one. 0 keeps it until it is dismissed. An undo toast always stays 8 seconds.
- `className`: `string`. Toaster. Classes for the corner the toasts sit in, like 'right-auto left-4'. Later classes win.
- `toast`: `(title | { title, description?, tone?, action?: { label, onClick }, timeout?, priority?, id?, onClose? }) => string`. Shows a toast and returns its id. Pressing the action closes the toast, then runs onClick. Showing again with the same id replaces it in place.
- `tone`: `"neutral" | "brand" | "danger" | "success" | "warning"`. Default `"neutral"`. In toast(). Colors and picks the icon. Neutral has none.
- `priority`: `"low" | "high"`. In toast(). 'high' reads it out at once. Default 'high' for danger and 'low' for the rest.
- `toast.promise`: `(work, { loading, success, error }) => Promise`. Shows loading with a spinner, then success or danger. Each is a title or toast options, and success and error can be functions of the result. Returns the same promise, which still rejects.
- `toast.undoable`: `({ title, description?, onUndo, onCommit?, undoLabel?, priority? }) => string`. Offers Undo for 8 seconds. Undo or ⌘Z runs onUndo once. Dismissing it, or the window ending, runs onCommit once. With no Toaster on the page, it commits at once.
- `toast.close`: `(id?) => void`. Closes one toast by id, or every toast with no id.
- `UNDO_WINDOW_MS`: `8000`. How long an undo toast stays, in milliseconds.
- `Toast`: `{ title?, description?, tone?, loading?, action? }`. A still picture of a toast, for docs and galleries. It takes no clicks, and a server file can render it. action is the button's text.
- `toastVariants`: `({ tone? }) => string`. The toast's look. toast.tsx is a client file, so call it from a client file.

## Accessibility

- Toasts are read out by a live region. The default priority waits until the screen reader is quiet. 'high', the default for danger, reads at once.
- Each tone has its own icon, so the tone does not rest on color alone. A loading toast sets aria-busy.
- F6 moves focus to the toast, so keyboard users can reach its action and Dismiss.
- The window holds while the pointer is on the toast, and while focus is in it.
- ⌘Z or Ctrl+Z undoes the newest change while its window is open. It leaves text fields alone, so their own undo still works.
- The Dismiss button is named for screen readers. It stays hidden from them until the toast is pointed at or focused.
- The toast uses the popover colors. The theme tests hold its muted text and each tone's icon color to 4.5:1 on popovers.

## Examples

### Archive at once, and offer Undo

```tsx
function EventRow({ id, title }: { id: string; title: string }) {
  const [hidden, setHidden] = useState(false);
  if (hidden) return null;
  return (
    <div className="flex items-center justify-between gap-3">
      {title}
      <Button
        emphasis="ghost"
        onClick={() => {
          setHidden(true);
          toast.undoable({
            title: `Archived ${title}`,
            onUndo: () => setHidden(false),
            onCommit: () => fetch(`/api/events/${id}/archive`, { method: "POST" }),
          });
        }}
      >
        Archive
      </Button>
    </div>
  );
}
```

### Place one Toaster in the root layout

```tsx
export default function RootLayout({ children }: { children: React.ReactNode }) {
  return (
    <html lang="en">
      <body>
        {children}
        <Toaster />
      </body>
    </html>
  );
}
```

### Follow an import to its end

```tsx
toast.promise(importMembers(file), {
  loading: "Importing members…",
  success: (added) => ({
    title: "Import finished",
    description: `${added} members added to the workspace.`,
    action: { label: "Open", onClick: () => router.push("/members") },
  }),
  error: "Could not import",
});
```

### A failure that stays until it is dismissed

```tsx
toast({
  title: "Reminder not sent",
  description: "Check your connection, then try again.",
  tone: "danger",
  action: { label: "Retry", onClick: sendReminder },
  timeout: 0,
});
```
