# Toast

An accessible React toast for Next.js: notifications that slide in, stack like a deck and swipe away, with a toast() function and Radix Toast parts.

Source: https://ui.fewtone.com/components/toast

## Installation

```bash
npx shadcn@latest add https://ui.fewtone.com/r/toast.json
```

## Usage

```tsx
import { Toaster, toast } from "@/components/ui/toast"

// Once, in your root layout
export function Providers({ children }: { children: React.ReactNode }) {
  return (
    <>
      {children}
      <Toaster />
    </>
  )
}

// Anywhere in a client component
export function SaveButton() {
  return <button onClick={() => toast.success("Changes saved")}>Save</button>
}
```

## API reference

### Toaster

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `position` | `"top-left" \| "top-center" \| "top-right" \| "bottom-left" \| "bottom-center" \| "bottom-right"` | `"bottom-right"` | Where toasts appear. They swipe towards the nearer side: left for left positions, right otherwise. |
| `duration` | `number` | `5000` | Milliseconds each toast stays open, unless it sets its own. |
| `expand` | `boolean` | `false` | Always show the toasts as a list, instead of a deck that fans out on hover or focus. |
| `visibleToasts` | `number` | `3` | How many toasts show at once. Older ones wait out of sight until newer ones close. |

### toast

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `toast(title, options?)` | `(title: ReactNode, options?: ToastOptions) => string` | – | Shows a toast. |
| `toast.success / toast.error` | `(title: ReactNode, options?: ToastOptions) => string` | – | With a check, or a cross and the title in the destructive colour. Both draw themselves in. |
| `toast.loading` | `(title: ReactNode, options?: ToastOptions) => string` | – | With a spinner. It stays until you update it (pass its id) or dismiss it. |
| `toast.promise` | `(promise, { loading, success, error }, options?) => string` | – | Shows loading until the promise settles, then success or error in the same toast. success and error can be functions of the result. |
| `toast.dismiss` | `(id?: string) => void` | – | Closes one toast, or every toast without an id. |
| `options.id` | `string` | – | Updates the toast with this id, if it's showing, instead of adding another. |
| `options.description` | `ReactNode` | – | A second line under the title. |
| `options.action` | `{ label: string; onClick?: (event) => void; altText?: string }` | – | A button such as Undo. It closes the toast after onClick, unless onClick calls preventDefault. altText (the label by default) says what it does for screen readers. |
| `options.duration` | `number` | – | Milliseconds before this toast closes; Infinity keeps it until it's dismissed. |

### ToastProvider

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `duration` | `number` | `5000` | Milliseconds a toast stays open, unless it sets its own. |
| `label` | `string` | `"Notification"` | Said by screen readers before each toast. |
| `swipeThreshold` | `number` | `50` | Pixels a swipe must travel to dismiss the toast; shorter swipes spring back. |

### Toast

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `open` | `boolean` | – | Whether it shows, when you control it. |
| `defaultOpen` | `boolean` | `true` | Whether it shows at first, when you don't. |
| `onOpenChange` | `(open: boolean) => void` | – | Called when it opens or closes, including when its time is up. |
| `variant` | `"default" \| "destructive"` | `"default"` | Destructive turns the title the destructive colour, on the same neutral surface. |
| `duration` | `number` | – | Milliseconds before it closes, overriding the provider's. |
| `type` | `"foreground" \| "background"` | `"foreground"` | Foreground (a result of what the person did) is read out at once; background waits its turn. |
| `onExitComplete` | `() => void` | – | Called once it has finished leaving, for example to drop it from your own list. |

### ToastViewport

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `position` | `ToastPosition` | `"bottom-right"` | The corner, or the middle of the top or bottom edge. |
| `expand` | `boolean` | `false` | Always show the toasts as a list. |
| `visibleToasts` | `number` | `3` | How many toasts show at once. |
| `hotkey` | `string[]` | `["F8"]` | The keys that move focus to the toasts. |
| `label` | `string` | `"Notifications ({hotkey})"` | The region's name for screen readers. |

### ToastTitle

| Prop | Type | Default | Description |
| --- | --- | --- | --- |

### ToastDescription

| Prop | Type | Default | Description |
| --- | --- | --- | --- |

### ToastAction

| Prop | Type | Default | Description |
| --- | --- | --- | --- |
| `altText` | `string` | – | How to do the same without the toast, for screen readers, which may hear the toast after it's gone. |

### ToastClose

| Prop | Type | Default | Description |
| --- | --- | --- | --- |

**When to use it:** Use toast() with one <Toaster /> for brief messages about something that just happened (saved, sent, failed), with an optional Undo action; use toast.promise for async work. Use Dialog when the person must respond before going on.
**Built on:** Radix Toast, Button, Spinner.
**npm packages (installed by the CLI):** class-variance-authority@^0.7.1, lucide-react@^1.52.0, motion@^14.0.0, radix-ui@^1.7.0.
**Installs these FewTone UI components with it:** `button`, `spinner`.

## Examples

### Types

Default, success, error and with an action. Show a few in a row: they stack like a deck, and hovering or focusing them fans them out into a list. Swipe one sideways to dismiss it.

```tsx
"use client";

import { Button } from "@/components/ui/button";
import {
  Select,
  SelectContent,
  SelectItem,
  SelectTrigger,
  SelectValue,
} from "@/components/ui/select";
import { Toaster, type ToastPosition, toast } from "@/components/ui/toast";
import { useId, useState } from "react";

const positions: { value: ToastPosition; label: string }[] = [
  { value: "top-left", label: "Top left" },
  { value: "top-center", label: "Top centre" },
  { value: "top-right", label: "Top right" },
  { value: "bottom-left", label: "Bottom left" },
  { value: "bottom-center", label: "Bottom centre" },
  { value: "bottom-right", label: "Bottom right" },
];

/*
 * One button per kind of toast, with the corner they appear in at the top right. Show a few in a row to see
 * them stack, then hover them to fan them out.
 */
export default function ToastTypes() {
  const id = useId();
  const [position, setPosition] = useState<ToastPosition>("bottom-right");

  return (
    <div className="flex w-full flex-col gap-6 self-stretch">
      <div className="-mt-6 -mr-6 flex items-center justify-end gap-2">
        <label htmlFor={`${id}-position`} className="text-sm text-muted-foreground">
          Position
        </label>
        <Select value={position} onValueChange={(value) => setPosition(value as ToastPosition)}>
          <SelectTrigger id={`${id}-position`} size="sm" className="w-36">
            <SelectValue />
          </SelectTrigger>
          <SelectContent>
            {positions.map((option) => (
              <SelectItem key={option.value} value={option.value}>
                {option.label}
              </SelectItem>
            ))}
          </SelectContent>
        </Select>
      </div>

      <div className="flex flex-1 flex-wrap items-center justify-center gap-3">
        <Button
          variant="outline"
          onClick={() => toast("Event created", { description: "Friday, 10:00 to 11:00" })}
        >
          Default
        </Button>
        <Button variant="outline" onClick={() => toast.success("Changes saved")}>
          Success
        </Button>
        <Button
          variant="outline"
          onClick={() =>
            toast.error("Couldn't save changes", { description: "Check your connection and try again." })
          }
        >
          Error
        </Button>
        <Button
          variant="outline"
          onClick={() =>
            toast("Message archived", {
              action: { label: "Undo", onClick: () => toast("Message restored") },
            })
          }
        >
          With action
        </Button>
      </div>

      <div aria-hidden className="-mb-6 h-(--size-control-sm)" />
      <Toaster position={position} />
    </div>
  );
}
```

### Promise

toast.promise shows a spinner until the work settles, then swaps the same toast to the result through the blur.

```tsx
"use client";

import { Button } from "@/components/ui/button";
import { Toaster, toast } from "@/components/ui/toast";
import { useId } from "react";

/** Pretend work that settles after a moment, one way or the other */
const work = (succeed: boolean) =>
  new Promise<void>((resolve, reject) =>
    setTimeout(() => (succeed ? resolve() : reject(new Error("Failed"))), 1500),
  );

/*
 * toast.promise shows a spinner until the work settles, then swaps the same toast to the result through the
 * blur, and it closes on its own from then.
 */
export default function ToastPromise() {
  const id = useId();

  return (
    <div className="flex flex-wrap items-start justify-center gap-x-6 gap-y-4">
      <div className="flex flex-col items-center gap-1.5">
        <Button
          onClick={() =>
            toast.promise(work(true), {
              loading: "Saving changes…",
              success: "Changes saved",
              error: "Couldn't save changes",
            })
          }
          aria-describedby={`${id}-saves`}
        >
          Save changes
        </Button>
        <span id={`${id}-saves`} className="text-xs text-muted-foreground">
          Succeeds
        </span>
      </div>
      <div className="flex flex-col items-center gap-1.5">
        <Button
          variant="outline"
          onClick={() =>
            toast.promise(work(false), {
              loading: "Publishing…",
              success: "Published",
              error: "Couldn't publish",
            })
          }
          aria-describedby={`${id}-fails`}
        >
          Publish
        </Button>
        <span id={`${id}-fails`} className="text-xs text-muted-foreground">
          Fails
        </span>
      </div>
      <Toaster />
    </div>
  );
}
```

### Composing the parts

For a toast you render yourself: ToastProvider, Toast with your own open state, and ToastViewport placed once. It still stacks and plays its exit.

```tsx
"use client";

import { Button } from "@/components/ui/button";
import {
  Toast,
  ToastAction,
  ToastClose,
  ToastDescription,
  ToastProvider,
  ToastTitle,
  ToastViewport,
} from "@/components/ui/toast";
import { useState } from "react";

/*
 * The parts, for a toast you render yourself: its open state is yours, and it plays its exit when it closes.
 * This one appears at the top centre.
 */
export default function ToastComposed() {
  const [open, setOpen] = useState(false);
  const [scheduled, setScheduled] = useState(true);

  return (
    <ToastProvider>
      <div className="flex flex-col items-center gap-2">
        <Button
          variant="outline"
          onClick={() => {
            setScheduled(true);
            setOpen(true);
          }}
        >
          Schedule meeting
        </Button>
        <span aria-live="polite" className="text-xs text-muted-foreground">
          {scheduled ? "Friday, 10:00" : "Not scheduled"}
        </span>
      </div>
      <Toast open={open} onOpenChange={setOpen}>
        <div className="grid flex-1 gap-0.5">
          <ToastTitle>Meeting scheduled</ToastTitle>
          <ToastDescription>Friday, 10:00 to 11:00</ToastDescription>
        </div>
        <ToastAction altText="Remove the meeting from Friday" onClick={() => setScheduled(false)}>
          Undo
        </ToastAction>
        <ToastClose />
      </Toast>
      <ToastViewport position="top-center" />
    </ToastProvider>
  );
}
```

## Related

- [Loading Button](https://ui.fewtone.com/components/loading-button.md): An accessible React loading button for Next.js that shows loading, success and error states for async actions, without changing width.
- [Progress](https://ui.fewtone.com/components/progress.md): An accessible React progress bar and ring for Next.js and Tailwind CSS that show how far along a task is, with an indeterminate state.
