Toast

Transient messages in the top layer, so they sit above whatever is on the page, including an open modal dialog, which is the case that breaks a z-index-based implementation.

import { Toast } from '@apostel/bedrock'
Loading demo…
Sourcetoast.tsx
import { useState } from 'react'
import { Toast } from '../../src/index'

/**
 * The viewport is a `popover="manual"` region, so toasts sit in the top layer
 * above whatever is on the page — including an open modal dialog, which is the
 * case that usually breaks a z-index-based implementation.
 */
export default function ToastDemo() {
  const [toasts, setToasts] = useState<number[]>([])
  const [next, setNext] = useState(1)

  return (
    <Toast.Provider duration={4000}>
      <button
        type="button"
        onClick={() => {
          setToasts((current) => [...current, next])
          setNext((value) => value + 1)
        }}
      >
        Show a toast
      </button>

      <Toast.Viewport>
        {toasts.map((id) => (
          <Toast.Root key={id}>
            <Toast.Title>Saved</Toast.Title>
            <Toast.Description>Change {id} was written to the server.</Toast.Description>
            <Toast.Action>Undo</Toast.Action>
            <Toast.Close>Dismiss</Toast.Close>
          </Toast.Root>
        ))}
      </Toast.Viewport>
    </Toast.Provider>
  )
}

Anatomy

<Toast.Provider>
  <Toast.Viewport>
    <Toast.Root>
      <Toast.Title />
      <Toast.Description />
      <Toast.Action />
      <Toast.Close />
    </Toast.Root>
  </Toast.Viewport>
</Toast.Provider>

Toast.Provider

Renders nothing. Takes duration in milliseconds, the default for every toast under it.

Toast.Viewport

Renders <ol popover="manual" role="region" aria-live="polite">.

choice why
popover="manual", not auto An auto popover light-dismisses, and a toast region that vanishes when you click the page is not a toast region.
aria-live="polite" New toasts are announced without interrupting.
<ol> Toasts are an ordered list, so a screen-reader user can navigate them as one.

This is the one imperative call in the library. No attribute opens a popover on parse, and the region has to exist in the top layer before the first toast arrives, so the ref calls showPopover(). Written down here rather than hidden, because it is the exception to the rule the library is built on.

Toast.Root

Renders <li data-bedrock-toast>.

prop type notes
duration number Overrides the provider's.
onOpenChange (open: boolean) => void Reports; cannot refuse.
asChild boolean The child must render an <li>.

It removes itself when its duration elapses.

Title, Description, Action, Close

<div>, <div>, <button>, <button>. Close removes the nearest toast; Action is a plain button you wire yourself.

Keyboard

key behaviour
Tab Reaches the viewport, which is focusable, then the buttons.

There is no F6 hotkey to jump to the toast region. Radix has one; this does not. See should you switch?.

What is not here