---
title: Toast
description: Brief notices for status updates and feedback.
group: Components
order: 68
---

Show a brief notice in the corner. Additional toasts stack behind the first, expand on hover, and dismiss with a downward or rightward swipe.

```tsx
"use client"

import { Button } from "@/components/ui/button"
import { toast } from "@/components/ui/toast"

export function Example() {
  return (
    <Button
      variant="outline"
      onClick={() => {
        const id = toast.add({
          title: "Event created",
          description: "Sunday, December 3 at 9:00 AM",
          actionProps: {
            children: "Undo",
            onClick() {
              toast.close(id)
            },
          },
        })
      }}
    >
      Show Toast
    </Button>
  )
}
```

## Installation

```bash
npx @arctis-sh/@arctis-sh/ui@latest add toast
```

Add `Toaster` near the root of your app.

```tsx
import { Toaster } from "@/components/ui/toast"

export default function Layout({ children }) {
  return (
    <html lang="en">
      <body>
        <Toaster>{children}</Toaster>
      </body>
    </html>
  )
}
```

## Usage

```tsx
import { toast } from "@/components/ui/toast"
```

```tsx
toast.add({
  title: "Event created",
  description: "Sunday, December 3 at 9:00 AM",
})
```

## Types

Set `type` to show a `success`, `info`, `warning`, or `error` icon. The `loading` type is used by `toast.promise`.

```tsx
"use client"

import { Button } from "@/components/ui/button"
import { toast } from "@/components/ui/toast"

export function Example() {
  return (
    <>
      <Button
        variant="outline"
        onClick={() =>
          toast.add({ description: "Event has been created." })
        }
      >
        Default
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.add({
            type: "success",
            description: "Event has been created.",
          })
        }
      >
        Success
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.add({
            type: "info",
            description: "Arrive 10 minutes before the event.",
          })
        }
      >
        Info
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.add({
            type: "warning",
            description: "The event cannot start before 8:00 AM.",
          })
        }
      >
        Warning
      </Button>
      <Button
        variant="outline"
        onClick={() =>
          toast.add({
            type: "error",
            description: "The event could not be created.",
            priority: "high",
          })
        }
      >
        Error
      </Button>
    </>
  )
}
```

## Action

Pass `actionProps` to add a small outline button that matches the close control's height.

```tsx
"use client"

import { Button } from "@/components/ui/button"
import { toast } from "@/components/ui/toast"

export function Example() {
  return (
    <Button
      variant="outline"
      onClick={() => {
        const id = toast.add({
          title: "Event created",
          actionProps: {
            children: "Undo",
            onClick() {
              toast.close(id)
            },
          },
        })
      }}
    >
      Show Toast
    </Button>
  )
}
```

```tsx
const id = toast.add({
  title: "Event created",
  actionProps: {
    children: "Undo",
    onClick() {
      toast.close(id)
    },
  },
})
```

## Promise

Use `toast.promise` to move one toast from loading to success or error.

```tsx
"use client"

import { Button } from "@/components/ui/button"
import { toast } from "@/components/ui/toast"

export function Example() {
  return (
    <Button
      variant="outline"
      onClick={() => {
        toast.promise(
          new Promise<{ name: string }>((resolve) => {
            window.setTimeout(() => resolve({ name: "Event" }), 2000)
          }),
          {
            loading: "Creating event…",
            success: (data) => `${data.name} created.`,
            error: "Could not create event.",
          },
        )
      }}
    >
      Create Event
    </Button>
  )
}
```

## API Reference

### toast

| Method | Type | Description |
| --- | --- | --- |
| add | (options) => string | Create a toast and return its id |
| close | (id?: string) => void | Dismiss one toast, or all if no id |
| update | (id, options) => void | Patch an existing toast |
| promise | (promise, options) => Promise | Drive loading → success/error |

```tsx
toast.add({ title: "Saved" })
```

### toast.add options

| Prop | Type | Default |
| --- | --- | --- |
| title | ReactNode | — |
| description | ReactNode | — |
| type | "success" \| "info" \| "warning" \| "error" \| "loading" \| string | — |
| timeout | number | 5000 |
| priority | "low" \| "high" | "low" |
| actionProps | button props | — |
| onClose | () => void | — |
| onRemove | () => void | — |

```tsx
toast.add({
  title: "Saved",
  description: "Your changes were stored.",
  type: "success",
})
```

### toast.promise options

| Prop | Type | Default |
| --- | --- | --- |
| loading | string \| options | — |
| success | string \| options \| (data) => … | — |
| error | string \| options \| (error) => … | — |

```tsx
toast.promise(save(), {
  loading: "Saving…",
  success: "Saved",
  error: "Something went wrong",
})
```

### Toaster

| Prop | Type | Default |
| --- | --- | --- |
| timeout | number | 5000 |
| limit | number | 3 |
| toastManager | ToastManager | shared manager |
| children | ReactNode | — |

Hover or focus the viewport to expand the stack. Toasts beyond `limit` stay mounted with `data-limited`, allowing them to fade instead of disappearing at once.

```tsx
<Toaster timeout={5000} limit={3} />
```

## Source

```tsx
"use client";

import { Toast as ToastPrimitive } from "@base-ui/react/toast";
import type { ComponentProps, ReactNode } from "react";
import { Button, buttonVariants } from "@/components/ui/button";
import { Spinner } from "@/components/ui/spinner";
import { cn } from "@/lib/utils";

const toastManager = ToastPrimitive.createToastManager();

export const toast = toastManager;
export const createToastManager = ToastPrimitive.createToastManager;
export const useToastManager = ToastPrimitive.useToastManager;

function mergeClass<State>(
  base: string,
  className?: string | ((state: State) => string | undefined),
) {
  if (typeof className === "function") {
    return (state: State) => cn(base, className(state));
  }
  return cn(base, className);
}

type ToastProviderProps = ComponentProps<typeof ToastPrimitive.Provider>;

export function ToastProvider({
  toastManager: manager = toastManager,
  ...props
}: ToastProviderProps) {
  return <ToastPrimitive.Provider toastManager={manager} {...props} />;
}

export function ToastPortal(
  props: ComponentProps<typeof ToastPrimitive.Portal>,
) {
  return <ToastPrimitive.Portal data-slot="toast-portal" {...props} />;
}

export function ToastViewport({
  className,
  ...props
}: ComponentProps<typeof ToastPrimitive.Viewport>) {
  return (
    <ToastPrimitive.Viewport
      data-slot="toast-viewport"
      className={mergeClass(
        "pointer-events-none fixed inset-x-4 bottom-4 z-50 mx-auto w-auto max-w-sm outline-none sm:right-4 sm:left-auto sm:mx-0 sm:w-full",
        className,
      )}
      {...props}
    />
  );
}

export function Toast({
  className,
  ...props
}: ComponentProps<typeof ToastPrimitive.Root>) {
  return (
    <ToastPrimitive.Root
      data-slot="toast"
      swipeDirection={["down", "right"]}
      className={mergeClass(
        [
          "group/toast pointer-events-auto absolute right-0 bottom-0 z-[calc(1000-var(--toast-index))] w-full origin-bottom select-none rounded-md border border-border bg-popover text-popover-foreground shadow-lg outline-none will-change-transform",
          "[--gap:0.75rem] [--height:var(--toast-frontmost-height,var(--toast-height))] [--offset-y:calc(var(--toast-offset-y)*-1+calc(var(--toast-index)*var(--gap)*-1)+var(--toast-swipe-movement-y))] [--peek:0.75rem] [--scale:calc(max(0,1-(var(--toast-index)*0.1)))] [--shrink:calc(1-var(--scale))]",
          "h-[var(--height)] [transform:translateX(var(--toast-swipe-movement-x))_translateY(calc(var(--toast-swipe-movement-y)-(var(--toast-index)*var(--peek))-(var(--shrink)*var(--height))))_scale(var(--scale))] [transition:transform_500ms_cubic-bezier(0.22,1,0.36,1),opacity_500ms,height_150ms]",
          "after:absolute after:top-full after:left-0 after:h-[calc(var(--gap)+1px)] after:w-full after:content-['']",
          "data-[expanded]:h-[var(--toast-height)] data-[expanded]:[transform:translateX(var(--toast-swipe-movement-x))_translateY(var(--offset-y))]",
          "data-[limited]:opacity-0 data-[starting-style]:[transform:translateY(150%)]",
          "[&[data-ending-style]:not([data-limited]):not([data-swipe-direction])]:[transform:translateY(150%)]",
          "data-[ending-style]:data-[swipe-direction=down]:[transform:translateY(calc(var(--toast-swipe-movement-y)+150%))]",
          "data-[ending-style]:data-[swipe-direction=left]:[transform:translateX(calc(var(--toast-swipe-movement-x)-150%))_translateY(var(--offset-y))]",
          "data-[ending-style]:data-[swipe-direction=right]:[transform:translateX(calc(var(--toast-swipe-movement-x)+150%))_translateY(var(--offset-y))]",
          "data-[ending-style]:data-[swipe-direction=up]:[transform:translateY(calc(var(--toast-swipe-movement-y)-150%))]",
        ].join(" "),
        className,
      )}
      {...props}
    />
  );
}

export function ToastContent({
  className,
  ...props
}: ComponentProps<typeof ToastPrimitive.Content>) {
  return (
    <ToastPrimitive.Content
      data-slot="toast-content"
      className={mergeClass(
        "flex h-full items-center gap-3 overflow-hidden p-4 transition-opacity duration-250 ease-[cubic-bezier(0.22,1,0.36,1)] data-[behind]:opacity-0 data-[expanded]:opacity-100",
        className,
      )}
      {...props}
    />
  );
}

export function ToastTitle({
  className,
  ...props
}: ComponentProps<typeof ToastPrimitive.Title>) {
  return (
    <ToastPrimitive.Title
      data-slot="toast-title"
      className={mergeClass("text-sm font-medium tracking-wide", className)}
      {...props}
    />
  );
}

export function ToastDescription({
  className,
  ...props
}: ComponentProps<typeof ToastPrimitive.Description>) {
  return (
    <ToastPrimitive.Description
      data-slot="toast-description"
      className={mergeClass(
        "text-sm tracking-wide text-muted-foreground",
        className,
      )}
      {...props}
    />
  );
}

export function ToastAction({
  className,
  ...props
}: ComponentProps<typeof ToastPrimitive.Action>) {
  return (
    <ToastPrimitive.Action
      data-slot="toast-action"
      className={mergeClass("shrink-0", className)}
      {...props}
    />
  );
}

function CloseIcon({ className }: { className?: string }) {
  return (
    <svg
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth="2"
      strokeLinecap="round"
      strokeLinejoin="round"
      aria-hidden="true"
      className={cn("size-3", className)}
    >
      <path d="M18 6 6 18" />
      <path d="m6 6 12 12" />
    </svg>
  );
}

export function ToastClose({
  className,
  children,
  ...props
}: ComponentProps<typeof ToastPrimitive.Close>) {
  return (
    <ToastPrimitive.Close
      data-slot="toast-close"
      aria-label="Close"
      className={mergeClass(
        buttonVariants({
          variant: "ghost",
          size: "icon-xs",
          className:
            "shrink-0 text-foreground/50 hover:bg-surface-hover hover:text-foreground",
        }),
        className,
      )}
      {...props}
    >
      {children ?? <CloseIcon />}
    </ToastPrimitive.Close>
  );
}

function ToastIcon({ type }: { type: string | undefined }) {
  if (!type) return null;

  const className = cn(
    "size-4 shrink-0",
    type === "success" && "text-success",
    type === "info" && "text-foreground",
    type === "warning" && "text-warning",
    type === "error" && "text-destructive",
    type === "loading" && "text-muted-foreground",
  );

  if (type === "success") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={className}>
        <circle cx="12" cy="12" r="10" />
        <path d="m9 12 2 2 4-4" />
      </svg>
    );
  }

  if (type === "info") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={className}>
        <circle cx="12" cy="12" r="10" />
        <path d="M12 16v-4" />
        <path d="M12 8h.01" />
      </svg>
    );
  }

  if (type === "warning") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={className}>
        <path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
        <path d="M12 9v4" />
        <path d="M12 17h.01" />
      </svg>
    );
  }

  if (type === "error") {
    return (
      <svg viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true" className={className}>
        <circle cx="12" cy="12" r="10" />
        <path d="m15 9-6 6" />
        <path d="m9 9 6 6" />
      </svg>
    );
  }

  if (type === "loading") {
    return <Spinner aria-hidden="true" className={className} />;
  }

  return null;
}

function ToastList() {
  const { toasts } = ToastPrimitive.useToastManager();

  return toasts.map((item) => (
    <Toast key={item.id} toast={item}>
      <ToastContent>
        <ToastIcon type={item.type} />
        <div className="flex min-w-0 flex-1 flex-col gap-0.5">
          {item.title ? <ToastTitle>{item.title}</ToastTitle> : null}
          {item.description ? (
            <ToastDescription>{item.description}</ToastDescription>
          ) : null}
        </div>
        {item.actionProps ? (
          <ToastAction
            render={<Button size="xs" variant="outline" />}
            {...item.actionProps}
          />
        ) : null}
        <ToastClose />
      </ToastContent>
    </Toast>
  ));
}

type ToasterProps = ToastProviderProps & {
  children?: ReactNode;
};

export function Toaster({
  children,
  toastManager: manager = toastManager,
  ...props
}: ToasterProps) {
  return (
    <ToastProvider toastManager={manager} {...props}>
      {children}
      <ToastPortal>
        <ToastViewport>
          <ToastList />
        </ToastViewport>
      </ToastPortal>
    </ToastProvider>
  );
}
```
