---
title: "Toast"
description: "A brief, self-dismissing confirmation of the reader's own action, built on Base UI Toast."
url: "https://opsinjs.pensievelabs.org/components/toast"
source: "https://opsinjs.pensievelabs.org/components/toast.md"
section: "Components"
status: "shipped"
kind: "component"
category: "feedback"
reviewed: "2026-09-20"
reviewer: "engineering"
aliases: ["snackbar", "transient message", "notification toast"]
implemented: true
---

> Elements written as `<PascalCase … />` below are opsinjs documentation
> components. Their attributes are the content: the values they render are
> generated from `tokens/*.json` and `registry/catalogue.ts` and are
> published separately at https://opsinjs.pensievelabs.org/r/index.json and under the Reference
> section.
> `<StubNotice>` IS THE EXCEPTION, AND IT IS THE ONE TO READ. It is a
> paired element rather than a self-closing one, and the text between
> its opening and closing tags is prose an author wrote, reproduced
> below word for word. That prose is where this page says whether the
> component has been reviewed. Read the children, not only the
> attributes.

<StubNotice
  name="toast"
  status="shipped"
  questions="[
  &#x22;Whether a self-dismissing confirmation is announced in full before it leaves has been reasoned about from the Base UI roles rather than heard in a screen reader.&#x22;,
  &#x22;Whether the default five seconds is long enough for a reader who reads slowly, uses a switch device, or is partway through another task is unmeasured.&#x22;,
  &#x22;Whether the title, the description and the close control clear the text and non-text contrast floors has been argued from the roles rather than measured.&#x22;,
]"
>
  Audited against WCAG 2.2 AA. Clinical review pending.
</StubNotice>

## Preview [#preview]

<ComponentPreview name="toast" />

## Installation [#installation]

<ComponentInstall name="toast" unbuilt="false" importPath="@/components/ui/toast" dependencies="[&#x22;@base-ui/react&#x22;, &#x22;lucide-react&#x22;]" />

## Usage [#usage]

```tsx
import { Toast } from "@/components/ui/toast"
import { Toast as ToastManager } from "@base-ui/react/toast"
```

```tsx
function SaveButton() {
  const manager = ToastManager.useToastManager()
  return (
    <button onClick={() => manager.add({ title: "Saved" })}>Save note</button>
  )
}

function Screen() {
  return (
    <Toast position="bottom-right">
      <SaveButton />
    </Toast>
  )
}
```

## When to use it [#when-to-use-it]

<WhenToUse
  use="[
  &#x22;Confirming an action the reader took, such as a note saved or a reminder set, where missing it costs nothing.&#x22;,
  &#x22;Acknowledging a background action that completed, such as a sync finishing, which the reader loses nothing by missing.&#x22;,
  &#x22;Offering a brief optional way to reverse the action, such as an Undo the reader may take or ignore.&#x22;,
]"
  avoid="[
  { case: &#x22;The message is a reading, a result, or anything about the reader's health they must not miss.&#x22;, instead: &#x22;alert-banner&#x22; },
  { case: &#x22;The message needs attention now and stays until the reader has dealt with it.&#x22;, instead: &#x22;alert-banner&#x22; },
  { case: &#x22;The information is a standing explanation, note or caveat that belongs on the surface while they read.&#x22;, instead: &#x22;callout&#x22; },
]"
/>

## Anatomy [#anatomy]

<Anatomy
  name="toast"
  parts="[
  {
    name: &#x22;Toast&#x22;,
    describes: &#x22;The region. Renders Base UI's provider and a fixed viewport, and mounts children inside so they can push a toast.&#x22;,
    prop: &#x22;position&#x22;,
  },
  {
    name: &#x22;Toast.Viewport&#x22;,
    describes: &#x22;The live region the stack sits in: role=\&#x22;region\&#x22;, aria-live=\&#x22;polite\&#x22;, named by label, reachable with F6.&#x22;,
    prop: &#x22;label&#x22;,
  },
  {
    name: &#x22;Toast.Item&#x22;,
    describes: &#x22;One toast, role=\&#x22;dialog\&#x22; labelled by its title. Card material, hairline and overlay shadow, so it reads as lifted.&#x22;,
  },
  {
    name: &#x22;Toast.Title&#x22;,
    describes: &#x22;The confirmation itself, such as \&#x22;Saved\&#x22;. The full foreground, drawn only when the pushed toast carries one.&#x22;,
  },
  {
    name: &#x22;Toast.Description&#x22;,
    describes: &#x22;An optional second line of detail. The muted role at the footnote step.&#x22;,
  },
  {
    name: &#x22;Toast.Action&#x22;,
    describes: &#x22;An optional quiet text control such as Undo, drawn when the pushed toast carries actionProps.&#x22;,
  },
  {
    name: &#x22;Toast.Close&#x22;,
    describes: &#x22;The dismiss control, a lucide X named by closeLabel, floored at the 44px target on both axes.&#x22;,
    prop: &#x22;closeLabel&#x22;,
  },
]"
/>

## Examples [#examples]

### A save confirmation [#a-save-confirmation]

A button pushes a "Saved" toast that stays a few seconds and then leaves. The words are fictional and name nothing measured.

<ComponentPreview name="toast-a-save-confirmation" kind="example" align="start" />

### With an action [#with-an-action]

`actionProps` adds one quiet control beside the message, so a reader who removed a note by mistake can reverse it. Here Undo only closes the toast.

<ComponentPreview name="toast-with-an-action" kind="example" align="start" />

## Content guidelines [#content-guidelines]

Keep a toast to the fact of the action, past tense, a word or two: "Saved", "Reminder set", "Note removed". Never write a clinical word, a reading or a level of urgency into one. Give an action a verb, and make it safe to ignore.

<DoDont>
  <DoDont.Do>
    **"Saved" with an optional "Undo".** The action still happened if the reader looks away.
  </DoDont.Do>

  <DoDont.Dont>
    **A toast carrying a number, a result or an urgency.** Whoever needs it most misses it.
  </DoDont.Dont>
</DoDont>

## Accessibility [#accessibility]

Audited against WCAG 2.2 AA in a source pass and a rendered pass that together raised no axe violation, no target-size hit and no 320px reflow failure. This is an author-run audit, not an independent review; clinical review is pending.

**The audit found nothing in the source to change.** Roles, names, the polite live region, pause on hover and focus, Escape and focus return all come correctly from `@base-ui/react`. The X icon is `aria-hidden` and the close carries a translatable `aria-label`. Colours are role tokens, type sizes are `text-opsin-*` tokens, and the controls floor their hit area at `--opsin-target-minimum`.

**Residual items to know.** A toast pushed with a description and no title yields a dialog with no accessible name, because `ToastList` rightly emits no empty title; the default and both examples always pass a title, and a description-only toast is still announced by the live region through `aria-describedby`. Whether an appearing toast overlaps a control focused elsewhere is a product-layout question outside component scope. Contrast pairs are unmeasured, so the report below carries what was measured.

<KeyboardTable
  name="toast"
  rows="[
  {
    keys: &#x22;F6&#x22;,
    action: &#x22;Moves focus to the toast viewport&#x22;,
    notes: &#x22;A Base UI global shortcut, so the stack is reachable without tabbing the page.&#x22;,
  },
  {
    keys: &#x22;Tab&#x22;,
    action: &#x22;Moves between the controls in the focused toast&#x22;,
    notes: &#x22;Two stops with an action, one without. Focus stays in the toast.&#x22;,
  },
  {
    keys: &#x22;Shift + Tab&#x22;,
    action: &#x22;Moves back through the controls in the focused toast&#x22;,
    notes: &#x22;The same stops in reverse.&#x22;,
  },
  {
    keys: &#x22;Enter, Space&#x22;,
    action: &#x22;Activates the focused control&#x22;,
    notes: &#x22;The close control or the action, both native buttons.&#x22;,
  },
  {
    keys: &#x22;Escape&#x22;,
    action: &#x22;Dismisses the focused toast&#x22;,
    notes: &#x22;So a reader never waits on the auto-dismiss timer.&#x22;,
  },
]"
/>

<ContrastReport component="toast" />

## API reference [#api-reference]

<PropsTable name="ToastProps" />

`position` defaults to `bottom-right`, `limit` to three and `timeout` to 5000ms. Passing `timeout={0}` stops every toast dismissing itself and raises a development warning, because a message that must persist is an AlertBanner or a Callout. `label` and `closeLabel` are props so they can be translated, and both default to English.

## Related [#related]

* [AlertBanner](./alert-banner.mdx) is for a message that needs attention now and stays until the reader deals with it.
* [Callout](./callout.mdx) sets a standing note or caveat apart for as long as the reader is on the surface.
