Skip to content
EMBOSS
Docs menu

Toast

Floating notification plates with an imperative handle — call toast.add anywhere and the mounted Toaster renders it.

@emboss/toast

Installation

pnpm dlx shadcn@latest add @emboss/toast

Usage

example.tsx
import { Button } from "@/components/ui/button";
import { toast, Toaster } from "@/components/ui/toast";

export function Example() {
  return (
    <>
      <Toaster />
      <Button
        onClick={() => {
          toast.add({
            title: "Preset written",
            description: "Slot A updated.",
          });
        }}
      >
        Write preset
      </Button>
    </>
  );
}

API reference

Toaster

Mount once near the app root; renders the stacked plates in the bottom-right corner.

PropTypeDefaultDescription
classNamestringExtends the viewport, e.g. to reposition the stack.

toast

The imperative manager. toast.add({ title, description, type }) returns an id; toast.close(id) dismisses; toast.promise(promise, { loading, success, error }) tracks an async task.

PropTypeDefaultDescription
timeoutnumber5000Auto-dismiss delay for an added toast, in milliseconds.

Keyboard

KeysAction
F6Moves focus to the toast viewport.

Source

View source — toast.tsx
toast.tsx
"use client";

import { Toast as BaseToast } from "@base-ui/react/toast";
import { X } from "lucide-react";
import { cn } from "@/lib/utils";

/**
 * The imperative toast handle. Import it anywhere and call
 * `toast.add({ title, description })` — the mounted Toaster renders it.
 * Also supports `toast.promise(promise, { loading, success, error })`.
 */
const toast = BaseToast.createToastManager();

function ToastList() {
  const { toasts } = BaseToast.useToastManager();
  return toasts.map((item) => (
    <BaseToast.Root
      key={item.id}
      toast={item}
      data-slot="toast"
      className="flex w-full items-start gap-3 rounded-md bg-surface-2 p-4 text-ink shadow-float-2 transition-[translate,opacity] duration-(--duration-float) ease-(--ease-float) data-ending-style:translate-y-2 data-ending-style:opacity-0 data-starting-style:translate-y-2 data-starting-style:opacity-0"
    >
      <BaseToast.Content className="flex min-w-0 flex-1 flex-col gap-0.5">
        <BaseToast.Title
          data-slot="toast-title"
          className="text-sm font-medium"
        />
        <BaseToast.Description
          data-slot="toast-description"
          className="text-sm text-ink-muted"
        />
      </BaseToast.Content>
      <BaseToast.Close
        aria-label="Dismiss notification"
        data-slot="toast-close"
        className="inline-flex size-6 shrink-0 cursor-pointer items-center justify-center rounded-sm text-ink-faint focus-ring transition-[box-shadow,color] hover:text-ink hover:shadow-flush"
      >
        <X className="size-3.5" aria-hidden />
      </BaseToast.Close>
    </BaseToast.Root>
  ));
}

/**
 * Mount once near the app root. Floating plates stack in the bottom-right
 * corner; swipe and timer behavior come from the underlying primitive.
 *
 * @example
 * <Toaster />
 * // elsewhere:
 * toast.add({ title: "Preset written", description: "Slot A updated." });
 */
function Toaster({ className }: { className?: string }) {
  return (
    <BaseToast.Provider toastManager={toast}>
      <BaseToast.Portal>
        <BaseToast.Viewport
          data-slot="toaster"
          className={cn(
            "fixed right-4 bottom-4 z-50 flex w-80 max-w-[calc(100vw-2rem)] flex-col gap-2",
            className,
          )}
        >
          <ToastList />
        </BaseToast.Viewport>
      </BaseToast.Portal>
    </BaseToast.Provider>
  );
}

export { Toaster, toast };