HextaUI

Toast

Brief messages that stack neatly, expand on hover, swipe away, and turn a loading state into success or error in place.

Used in Pro blocks: Artifact, Chat Sidebar, Chat Thread, and 7 more

"use client"

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

export function ToastDemo() {
  return (
    <Button
      variant="outline"
      onClick={() =>
        toast("Event created", {
          description: "Sunday, December 3 at 9:00 AM",
          action: { label: "Undo", onClick: () => toast("Event removed") },
        })
      }
    >
      Show toast
    </Button>
  )
}
pnpm dlx shadcn@latest add https://hextaui.com/r/toast.json

Adds the component, the HextaUI theme tokens and any HextaUI components it depends on.

Add <Toaster /> once, near the root of your app.

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

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

Then call toast from anywhere, including outside React components.

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

toast("Event created", { description: "Sunday at 9:00 AM" })
toast.success("Payment received")
toast.promise(save(), { loading: "Saving…", success: "Saved", error: "Couldn't save" })

Toasts stack into a neat pile with the newest in front. Hover or focus the pile to fan it out; every timer pauses while you read. Swipe a toast toward its edge to dismiss it.

Every toast uses the same calm surface; only the icon carries the color.

"use client"

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

export function ToastTypes() {
  return (
    <div className="flex flex-wrap justify-center gap-2">
      <Button variant="outline" size="sm" onClick={() => toast("Draft saved")}>
        Default
      </Button>
      <Button
        variant="outline"
        size="sm"
        onClick={() => toast.success("Payment received")}
      >
        Success
      </Button>
      <Button
        variant="outline"
        size="sm"
        onClick={() =>
          toast.error("Couldn't upload photo", {
            description: "The file is larger than 10 MB.",
          })
        }
      >
        Error
      </Button>
      <Button
        variant="outline"
        size="sm"
        onClick={() => toast.warning("Storage almost full")}
      >
        Warning
      </Button>
      <Button
        variant="outline"
        size="sm"
        onClick={() => toast.info("A new version is available")}
      >
        Info
      </Button>
    </div>
  )
}

toast.promise shows a spinner, then turns into a success or error toast in place, with the icon popping in as it changes.

"use client"

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

let attempt = 0

function deploy() {
  attempt += 1
  const fails = attempt % 2 === 0
  return new Promise<{ url: string }>((resolve, reject) =>
    setTimeout(
      () =>
        fails
          ? reject(new Error("Build failed"))
          : resolve({ url: "hextaui.com" }),
      1800
    )
  )
}

export function ToastPromise() {
  return (
    <Button
      variant="outline"
      onClick={() =>
        toast.promise(deploy(), {
          loading: "Deploying…",
          success: (data) => ({
            title: "Deployed",
            description: `Live at ${data.url}`,
          }),
          error: (error) => ({
            title: "Deploy failed",
            description: error instanceof Error ? error.message : "Try again.",
          }),
        })
      }
    >
      Deploy (fails every other time)
    </Button>
  )
}

Pass action for a button such as Undo. Clicking it also dismisses the toast.

"use client"

import * as React from "react"

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

export function ToastAction() {
  const [archived, setArchived] = React.useState(0)

  return (
    <div className="flex flex-col items-center gap-3">
      <Button
        variant="outline"
        onClick={() => {
          setArchived((count) => count + 1)
          toast("Conversation archived", {
            action: {
              label: "Undo",
              onClick: () => setArchived((count) => count - 1),
            },
          })
        }}
      >
        Archive
      </Button>
      <p className="text-sm text-muted-foreground tabular-nums">
        {archived} archived
      </p>
    </div>
  )
}

Keep the id from toast.loading and change it with toast.update, including its type.

"use client"

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

export function ToastUpdate() {
  return (
    <Button
      variant="outline"
      onClick={() => {
        const id = toast.loading("Uploading 3 files…")
        setTimeout(() => toast.update(id, { title: "Uploading 2 of 3…" }), 900)
        setTimeout(
          () =>
            toast.update(id, {
              type: "success",
              title: "Upload complete",
              description: "3 files added to Photos.",
            }),
          1800
        )
      }}
    >
      Upload files
    </Button>
  )
}

Give a toast an id and calling it again updates the existing one instead of stacking a copy.

"use client"

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

export function ToastDedupe() {
  return (
    <Button
      variant="outline"
      onClick={() =>
        toast.info("You're offline", {
          id: "offline",
          description: "Changes will sync when you reconnect.",
        })
      }
    >
      Go offline (click twice)
    </Button>
  )
}
KeyAction
F6Moves focus into the toasts and fans them out.
TabMoves between toast actions and close buttons.
EscDismisses the focused toast.
  • Toasts are announced politely; toast.error is announced right away.
  • Timers pause while the pointer or focus is on the toasts, so nobody has to race a countdown.
  • Close buttons appear on hover and focus, and are always visible on touch screens.
  • With reduced motion, toasts fade instead of sliding.
PropTypeDefault
positionPhones always use the full width.
"top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right""bottom-right"
limitToasts shown at once; older ones fade out.
number3
timeoutDefault time on screen, in ms.
number5000
AttributeDescription
data-slot="toaster"The viewport, with data-position.
data-slot="toast"Each toast, with data-type, data-expanded and data-swiping.
PropTypeDefault
description
ReactNode–
action
{ label: ReactNode; onClick?: (event) => void }–
idReuse to update instead of duplicating.
string–
timeoutDefaults to the Toaster timeout. Long messages stay up longer, about 250 words a minute.
number–
priority
"low" | "high""low"
onClose
() => void–
onRemove
() => void–

Also toast.success, toast.error, toast.warning, toast.info, toast.loading, toast.promise, toast.update and toast.dismiss. Each returns or takes the toast's id.

Used in Pro blocks

Blocks that build on Toast.

Every block comes with HextaUI Pro, and Prompt Input is free.