AZMARA PLATFORM
Components

Toast

Short notifications raised from anywhere with toast.success(), toast.error() and toast.promise(). Stacked, pausable, swipeable and announced to screen readers. Built on Base UI.

Usage

Mount one Toaster near the root of your app, then raise toasts from anywhere, including outside React.

import { Toaster, toast } from "@azmr/ui/toast";

// Once, in the root layout:
<Toaster />

// Anywhere:
toast.success("Invoice sent");

Toaster is a client component. A toast raised before it has mounted has nowhere to render, so put it in the layout rather than in a page.

This site has a Toaster in its layout, so the demos below raise real toasts in the corner of the screen.

Types

Preview theme
Preview density
"use client";

import { Button } from "@azmr/ui/button";
import { toast } from "@azmr/ui/toast";

export default function ToastTypes() {
  return (
    <div className="flex flex-wrap gap-2">
      <Button variant="outline" onClick={() => toast("Draft saved")}>
        Plain
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.success("Invoice sent", { description: "Aroha will get it within a minute." })
        }
      >
        Success
      </Button>
      <Button variant="outline" onClick={() => toast.info("New version available")}>
        Info
      </Button>
      <Button variant="outline" onClick={() => toast.warning("Your plan renews tomorrow")}>
        Warning
      </Button>
      {/* Errors are announced urgently (role="alert"); the others politely. */}
      <Button
        variant="outline"
        onClick={() => toast.error("Could not save", { description: "Check your connection." })}
      >
        Error
      </Button>
    </div>
  );
}
CallIconAnnounced
toast(title)nonepolitely
toast.successcheckpolitely
toast.infoinfopolitely
toast.warningtrianglepolitely
toast.erroralerturgently (role="alert")
toast.loadingspinnerpolitely

Each takes (title, options). Options: description, duration, id, priority, action and onClose. Every call returns the toast's id.

Icons are decorative; the type is also in the text you write. Do not rely on colour alone.

Actions and updating a toast

Preview theme
Preview density
  • Quarterly report
  • Team photo
"use client";

import { Button } from "@azmr/ui/button";
import { toast } from "@azmr/ui/toast";
import { useState } from "react";

export default function ToastActions() {
  const [items, setItems] = useState(["Quarterly report", "Team photo"]);

  function remove(name: string) {
    setItems((all) => all.filter((n) => n !== name));
    toast(`Deleted “${name}”`, {
      // The same id replaces the toast instead of stacking another.
      id: `deleted-${name}`,
      action: {
        label: "Undo",
        onClick: () => setItems((all) => (all.includes(name) ? all : [...all, name])),
      },
    });
  }

  return (
    <div className="flex flex-col items-start gap-3">
      <ul className="flex flex-col gap-2 text-sm">
        {items.map((name) => (
          <li key={name} className="flex items-center gap-3">
            {name}
            <Button size="sm" variant="ghost" onClick={() => remove(name)}>
              Delete
            </Button>
          </li>
        ))}
        {items.length === 0 && <li className="text-muted-foreground">Nothing left.</li>}
      </ul>
    </div>
  );
}

action: { label, onClick } adds a button, and the toast dismisses itself after it runs. Undo is the classic use.

Giving a toast an id that is already showing updates it instead of stacking another, so repeating an action does not flood the screen. toast.update(id, { title, type, … }) changes a toast in place, and toast.dismiss(id) removes one (toast.dismiss() removes them all).

Promises

Preview theme
Preview density
"use client";

import { Button } from "@azmr/ui/button";
import { toast } from "@azmr/ui/toast";

const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms));

export default function ToastPromise() {
  return (
    <div className="flex flex-wrap gap-2">
      <Button
        variant="outline"
        onClick={() =>
          toast.promise(
            wait(1500).then(() => 12),
            {
              loading: "Exporting…",
              success: (rows) => `Exported ${rows} rows`,
              error: "Export failed",
            },
          )
        }
      >
        Export (succeeds)
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast
            .promise(
              wait(1500).then(() => Promise.reject(new Error("Disk full"))),
              {
                loading: "Exporting…",
                success: "Exported",
                error: (e: Error) => `Export failed: ${e.message}`,
              },
            )
            .catch(() => {})
        }
      >
        Export (fails)
      </Button>
    </div>
  );
}

toast.promise(promise, { loading, success, error }) shows a loading toast, then replaces it with the result. success and error can be functions of the value or the error. It returns the same promise, so it still rejects for you to handle; a failure is announced urgently.

Timing

A toast dismisses itself after duration (5000 ms by default; set it on the Toaster or per toast). duration: 0 keeps it until dismissed, and toast.loading never times out. The timer pauses while the pointer is over the stack or focus is inside it, so nobody loses a toast they are reading.

Do not use a toast for anything the person must act on. Auto-dismissing messages are easy to miss, so keep errors that need a decision in the page (or an AlertDialog).

Stacking and position

The Toaster shows limit toasts (3 by default); older ones wait behind and the stack fans out on hover or focus. position is top-left, top-center, top-right, bottom-left, bottom-center or bottom-right (default), and new toasts come in from that edge. The stack sits on the --z-toast layer, above dialogs and menus.

Motion

Toasts slide in from their edge, fade out, and the stack reflows on --motion-duration-slow. Under prefers-reduced-motion those collapse to near zero. While a finger is dragging a toast there is no easing, so it tracks exactly.

Accessibility

Keyboard interactions
KeyAction
F6Moves focus into the notifications region. Tab then moves between the actions and close buttons.
TaborShift+TabMoves between the toasts' buttons. The timers pause while focus is inside.
EnterorSpaceActivates the focused action or close button.
  • The region is named "Notifications" (change it with label) and is a polite live region. Errors are also written to a separate alert, so they are spoken at once.
  • The close button is labelled "Dismiss notification"; its icon is decorative.
  • A toast can be swiped away toward the edge it came from on touch.

Props

Prop

Type

Prop

Type

On this page