Open UI

Toast

A toast is a non-modal, unobtrusive, and time-limited notification that provides succinct feedback about an action.

import { Button, toast } from "@opengovsg/oui"export const Example = () => {  return (    <Button onPress={() => toast("This is a toast message")}>Show toast</Button>  )}

Usage

OUI's toast component is built on top of Sonner, a minimal toast notification library.

To use toasts in your application, you need to:

  1. Add the Toaster component to your app's layout (typically in your root layout), wrapped in a data-react-aria-top-layer="true" element
  2. Use the toast function to trigger notifications
import { toast, Toaster } from "@opengovsg/oui"
layout.tsx
export default function RootLayout({ children }) {
  return (
    <html>
      <body>
        {children}
        <div data-react-aria-top-layer="true">
          <Toaster />
        </div>
      </body>
    </html>
  )
}

Why the data-react-aria-top-layer wrapper?

Many OUI components (Modal, Dialog, Popover, Select, etc.) are built on React Aria Components. When one of these opens, React Aria calls ariaHideOutside and sets aria-hidden="true" on every sibling of the overlay so that assistive technologies only announce content inside it.

Sonner renders the Toaster as a sibling of your app root. Without opting out, any toast fired while a modal is open would be hidden from screen readers (and flagged by accessibility tooling such as axe). React Aria treats any ancestor element with data-react-aria-top-layer="true" as part of the top layer and skips it when hiding outside content, which keeps toasts announceable regardless of what overlay is open.

page.tsx
import { toast } from "@opengovsg/oui"

function MyComponent() {
  return (
    <Button onPress={() => toast("This is a toast message")}>Show toast</Button>
  )
}

Alternatively, install the component as local source via the shadcn CLI:

npx shadcn@latest add https://oui.open.gov.sg/r/toast.json
pnpm dlx shadcn@latest add https://oui.open.gov.sg/r/toast.json
npx shadcn@latest add https://oui.open.gov.sg/r/toast.json
bunx --bun shadcn@latest add https://oui.open.gov.sg/r/toast.json

Examples

With Description

You can add a description to provide additional context to your toast message.

import { Button, toast } from "@opengovsg/oui"export const Example = () => {  return (    <Button      onPress={() =>        toast("Post updated", {          description: "Your post has been successfully updated.",        })      }    >      Show toast    </Button>  )}

Types

Use different toast types to convey the nature of the notification. Available types are success, error, warning, info, and loading.

import { Button, toast } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex flex-wrap gap-3">      <Button variant="outline" onPress={() => toast("Default toast")}>        Default      </Button>      <Button        variant="outline"        onPress={() => toast.success("Successfully saved!")}      >        Success      </Button>      <Button        variant="outline"        onPress={() => toast.error("Something went wrong")}      >        Error      </Button>      <Button        variant="outline"        onPress={() => toast.warning("Please review your input")}      >        Warning      </Button>      <Button        variant="outline"        onPress={() => toast.info("Email is already registered")}      >        Info      </Button>      <Button variant="outline" onPress={() => toast.loading("Loading...")}>        Loading      </Button>    </div>  )}

Positions

Control where the toast appears on the screen using the position option. Available positions are top-left, top-center, top-right, bottom-left, bottom-center, and bottom-right.

import { Button, toast } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex flex-col gap-4">      <div className="flex flex-wrap gap-3">        <Button          variant="outline"          size="sm"          onPress={() => toast("Top left toast", { position: "top-left" })}        >          Top Left        </Button>        <Button          variant="outline"          size="sm"          onPress={() => toast("Top center toast", { position: "top-center" })}        >          Top Center        </Button>        <Button          variant="outline"          size="sm"          onPress={() => toast("Top right toast", { position: "top-right" })}        >          Top Right        </Button>      </div>      <div className="flex flex-wrap gap-3">        <Button          variant="outline"          size="sm"          onPress={() =>            toast("Bottom left toast", { position: "bottom-left" })          }        >          Bottom Left        </Button>        <Button          variant="outline"          size="sm"          onPress={() =>            toast("Bottom center toast", { position: "bottom-center" })          }        >          Bottom Center        </Button>        <Button          variant="outline"          size="sm"          onPress={() =>            toast("Bottom right toast", { position: "bottom-right" })          }        >          Bottom Right        </Button>      </div>    </div>  )}

Actions

Add interactive action buttons to your toasts. You can include an action button, a cancel button, or both.

import { Button, toast } from "@opengovsg/oui"export const Example = () => {  return (    <div className="flex flex-wrap gap-3">      <Button        variant="outline"        onPress={() =>          toast.info("New comment on your post!", {            action: {              label: "View",              onClick: () => console.log("Viewed"),            },          })        }      >        With Action      </Button>      <Button        variant="outline"        onPress={() =>          toast.warning("Are you sure?", {            action: {              label: "Confirm",              onClick: () => console.log("Confirmed"),            },            cancel: {              label: "Cancel",              onClick: () => console.log("Cancelled"),            },          })        }      >        Action + Cancel      </Button>      <Button        variant="outline"        onPress={() =>          toast.error("Failed to save", {            cancel: {              label: "Dismiss",              onClick: () => console.log("Dismissed"),            },          })        }      >        With Cancel      </Button>    </div>  )}

Promise

Use toast.promise() to show loading, success, and error states for async operations automatically.

import { Button, toast } from "@opengovsg/oui"function wait(ms: number) {  return new Promise((resolve) => setTimeout(resolve, ms))}export const Example = () => {  const handleSave = () => {    toast.promise(wait(2000), {      loading: "Saving changes...",      success: "Changes saved successfully!",      error: "Failed to save changes.",    })  }  return <Button onPress={handleSave}>Save changes</Button>}

API Reference

Toaster

The Toaster component renders the toast container. It should be placed once in your application, typically in your root layout.

PropTypeDefaultDescription
position"top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right""bottom-right"Default position for all toasts
expandbooleanfalseWhether toasts should be expanded by default
durationnumber4000Default duration in milliseconds before a toast auto-dismisses
closeButtonbooleantrueWhether to show the close button on toasts
richColorsbooleanfalseWhether to use rich colors for toast types
classNamesSlotsToClasses<ToastSlots>-Custom Tailwind classes for individual toast slots
variant"bordered""bordered"The visual style variant applied to all toasts

Toast slots (via classNames on Toaster):

Slots are named regions of the toast you can target with custom Tailwind classes via the classNames prop on Toaster. Each slot below corresponds to a key on the classNames object.

  • base: The outermost Toaster wrapper element.
  • toast: The individual toast item container.
  • title: The toast title text element.
  • description: The toast description text element.
  • content: The wrapper around the title and description.
  • icon: The type icon (success, error, info, warning) rendered to the left.
  • closeButton: The close button element on each toast.
  • actionButton: The primary action button element.
  • cancelButton: The cancel/secondary action button element.
  • loader: The progress loader element (used for toast.loading toasts).

toast()

The toast function is used to trigger notifications. It returns a unique toast ID that can be used to dismiss or update the toast.

// Basic usage
toast("Message")

// With options
toast("Message", {
  description: "Additional details",
  duration: 5000,
  position: "top-center",
})

// Type-specific toasts
toast.success("Success message")
toast.error("Error message")
toast.warning("Warning message")
toast.info("Info message")
toast.loading("Loading message")

// Promise toast
toast.promise(asyncFunction, {
  loading: "Loading...",
  success: "Success!",
  error: "Error!",
})

// Dismiss a toast
const toastId = toast("Message")
toast.dismiss(toastId)

// Dismiss all toasts
toast.dismiss()

Toast Options

OptionTypeDescription
descriptionstringAdditional text displayed below the title
durationnumberDuration in milliseconds before the toast auto-dismisses
positionPositionOverride the default position for this toast
action{ label: string; onClick: () => void }Add an action button to the toast
cancel{ label: string; onClick: () => void }Add a cancel button to the toast
idstring | numberCustom ID for the toast (useful for updating or dismissing)
onDismiss(toast: Toast) => voidCallback when the toast is dismissed
onAutoClose(toast: Toast) => voidCallback when the toast auto-closes

For a complete list of options and advanced usage, refer to the Sonner documentation.

On this page