caliper uidocs

Toast

Free

A short, dismissible message that confirms an action, with an optional undo.

Installs components/ui/toast.tsxDepends on @radix-ui/react-slot, @radix-ui/react-toast, lucide-reactMotion shared motion tokens
pnpm dlx shadcn@latest add https://caliperui.site/r/toast.json

    When to use #

    Use it for

    • Confirming something the person just did, such as archiving a project.
    • Offering Undo for a reversible action.

    Skip it when

    • The message must be read before continuing; use an alert or dialog.
    • The content is long; keep toasts to a title and one line.

    Decisions #

    Why it is built this way, so you know what to keep when you change your copy.

    Behaviour

    Loading resolves in place
    A toast that starts as “Saving…” becomes “Saved” or the error where it is, instead of stacking a second toast.
    Errors and loading never time out
    A message that needs action or is still working stays until it is dealt with; only plain confirmations leave on their own.

    Styling

    Frosted, because it floats
    Toasts use the shared frosted surface with a 14px radius; the reading canvas underneath stays solid.

    Installation #

    terminal
    pnpm dlx shadcn@latest add https://caliperui.site/r/toast.json

    Adds its 4 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

    Usage #

    This is the code behind the preview above. It is built from Toast, ToastProvider and ToastStack.

    example.tsx
    import { useState } from "react"
    import { Button } from "@/components/ui/button"
    import { Toast, ToastProvider, ToastStack } from "@/components/ui/toast"
    
    export function ArchiveToast() {
      const [sending, setSending] = useState<"idle" | "pending" | "done">("idle");
      const [failed, setFailed] = useState(false);
      return (
        <ToastProvider duration={5000}>
          <div className="flex flex-wrap justify-center gap-2">
            <Button
              onClick={() => {
                setSending("pending");
                window.setTimeout(() => setSending("done"), 1600);
              }}
            >
              Send 3 invoices
            </Button>
            <Button variant="secondary" className="text-danger" onClick={() => setFailed(true)}>Charge failing card</Button>
          </div>
          <Toast
            title={sending === "pending" ? "Sending 3 invoices…" : "Sent 3 invoices"}
            loading={sending === "pending"}
            variant="success"
            open={sending !== "idle"}
            onOpenChange={(open) => !open && setSending("idle")}
          >
            {sending === "pending" ? "Ostra Freight, Halden Analytics, Lumen Labs" : "Each customer got a PDF and a pay link."}
          </Toast>
          <Toast
            title="Lumen Labs card declined"
            variant="error"
            open={failed}
            onOpenChange={setFailed}
            action={{ label: "Email Lumen Labs", altText: "Ask Lumen Labs to update their card", onClick: () => setFailed(false) }}
          >
            Insufficient funds. We will retry on 12 Oct. Errors stay until you dismiss them.
          </Toast>
          <ToastStack />
        </ToastProvider>
      );
    }
    

    API reference #

    Toast

    component

    Also accepts the native props of its root element and a ref.

    PropTypeDefault
    title *ReactNode—
    variantFeedbackVariant"neutral"
    loadingbooleanfalse
    childrenReactNode—
    action{ label: string; altText: string; onClick: MouseEventHandler<HTMLButtonElement> | undefined; disabled?: boolean | undefined; }—

    ToastProvider

    component

    No props of its own; it accepts the native props of its root element.

    ToastStack

    component

    No props of its own; it accepts the native props of its root element.

    Accessibility #

    It is built on native elements and keeps aria-busy, aria-labelledby, aria-describedby, aria-label, aria-hidden and aria-live in step with state. Changes that matter are announced through a live region.

    Keyboard and focus handling comes from Radix toast: arrow keys move through items, Enter and Space select, and Escape closes and returns focus.

    Motion #

    Transitions use the shared duration and easing tokens. Under prefers-reduced-motion those tokens collapse to instant and travel distances go to zero, so state changes still happen without movement.