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:
- Add the
Toastercomponent to your app's layout (typically in your root layout), wrapped in adata-react-aria-top-layer="true"element - Use the
toastfunction to trigger notifications
import { toast, Toaster } from "@opengovsg/oui"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.
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.jsonpnpm dlx shadcn@latest add https://oui.open.gov.sg/r/toast.jsonnpx shadcn@latest add https://oui.open.gov.sg/r/toast.jsonbunx --bun shadcn@latest add https://oui.open.gov.sg/r/toast.jsonExamples
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.
| Prop | Type | Default | Description |
|---|---|---|---|
| position | "top-left" | "top-center" | "top-right" | "bottom-left" | "bottom-center" | "bottom-right" | "bottom-right" | Default position for all toasts |
| expand | boolean | false | Whether toasts should be expanded by default |
| duration | number | 4000 | Default duration in milliseconds before a toast auto-dismisses |
| closeButton | boolean | true | Whether to show the close button on toasts |
| richColors | boolean | false | Whether to use rich colors for toast types |
| classNames | SlotsToClasses<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 outermostToasterwrapper 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 fortoast.loadingtoasts).
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
| Option | Type | Description |
|---|---|---|
| description | string | Additional text displayed below the title |
| duration | number | Duration in milliseconds before the toast auto-dismisses |
| position | Position | Override 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 |
| id | string | number | Custom ID for the toast (useful for updating or dismissing) |
| onDismiss | (toast: Toast) => void | Callback when the toast is dismissed |
| onAutoClose | (toast: Toast) => void | Callback when the toast auto-closes |
For a complete list of options and advanced usage, refer to the Sonner documentation.