Presentation
Notification
Tell people something happened, without stopping what they’re doing.
"use client"
import { Button } from "macbetter/button"
import { toast } from "macbetter/toast"
export default function Example() {
return (
<Button
variant="filled"
onClick={() =>
toast("Hollis Grant", {
description: "Can you review the Q3 roadmap before Friday’s planning meeting?",
data: { icon: <img src="/app-icon.svg" alt="" /> },
})
}
>
Send Notification
</Button>
)
}When to use it
Use a notification for news that doesn’t need an answer: a message arrived, an export finished, a sync failed in the background. It appears, waits a few seconds and goes. Don’t notify about something people just did themselves and can already see. Anything people must act on belongs in an Alert or on the page.
- Lead with who or what (“Hollis Grant”, “Export complete”) and keep the description to a sentence.
- Offer at most one action, like Undo.
Usage
Mount Toaster once, near the root of your app, then call toast() from anywhere: an event handler, a server action’s result, a store.
// app/layout.tsx
import { Toaster } from "macbetter/toast"
export default function RootLayout({ children }) {
return (
<html lang="en">
<body>
{children}
<Toaster />
</body>
</html>
)
}import { toast } from "macbetter/toast"
toast("Hollis Grant", { description: "Can you review the Q3 roadmap?", data: { icon: <img src="/icon.png" alt="" /> } })data.icon is the app icon; data.time replaces “now”. Each notification shows for five seconds; hovering or focusing the stack pauses it.
Types
toast.success, .warning, .error, .info and .loading give a notification a colored glyph when you don’t pass an icon. toast.promise() shows a loading notification and turns it into the result.
"use client"
import { Button } from "macbetter/button"
import { toast } from "macbetter/toast"
function upload() {
return new Promise((resolve) => setTimeout(resolve, 2000))
}
export default function Example() {
return (
<>
<Button onClick={() => toast.success("Export complete", { description: "Q3 Roadmap.pdf is in Downloads." })}>
Success
</Button>
<Button onClick={() => toast.warning("Storage almost full", { description: "1.2 GB of 15 GB left." })}>
Warning
</Button>
<Button onClick={() => toast.error("Couldn’t sync", { description: "Check your connection and try again." })}>
Error
</Button>
<Button
onClick={() =>
toast.promise(upload, { loading: "Uploading 12 photos…", success: "Photos uploaded", error: "Upload failed" })
}
>
Loading
</Button>
</>
)
}Action
Pass an action for one follow-up, like Undo.
"use client"
import { Button } from "macbetter/button"
import { toast } from "macbetter/toast"
export default function Example() {
return (
<Button
onClick={() =>
toast("Message archived", {
description: "“Lunch on Thursday?” moved to Archive.",
action: { label: "Undo", onAction: () => toast("Message restored") },
})
}
>
Archive Message
</Button>
)
}More than one host
createToaster() makes a separate queue for a Toaster of its own, like the phone preview below.
"use client"
import { Button } from "macbetter/button"
import { createToaster, Toaster } from "macbetter/toast"
import { useState } from "react"
export default function Example() {
const [notify] = useState(() => createToaster())
return (
<>
<Button
variant="filled"
onClick={() =>
notify("Kofi Mensah", {
description: "Lunch on Thursday? There’s a new ramen place on Valencia.",
data: { icon: <img src="/app-icon.svg" alt="" /> },
})
}
>
Show Banner
</Button>
<Toaster toaster={notify} />
</>
)
}Platform behavior
- macOS. A 344pt card of thick material at the top right: app icon, bold title, the time at the trailing edge, and up to four lines of description. It slides in from the edge it’s aligned to: the right, or the left in a right-to-left layout. Newer notifications stack on top, with older ones peeking out below; hover the stack to spread it out. A close button appears on the card’s corner on hover.
- iOS and iPadOS. A banner that drops from the top of the screen, centered, with larger corners and 15pt text. Swipe it up to dismiss it. The close button stays hidden unless you reach it with the keyboard.
Toasterreads the platform where it’s mounted, so a host inside a phone preview behaves like a phone and drops its banners from the top of that preview rather than the window. Reduced motion fades notifications in place instead of sliding them.
Accessibility
- Each notification is announced once through a live region: politely, or right away for errors. The region doesn’t take focus.
- F6 moves focus into the stack and back. Tab moves between notifications and their buttons; Escape dismisses the focused one.
- The close button is named “Close”. Timers pause while the pointer or focus is in the stack, and while the page is hidden.
API
Toaster
Takes every ui-sdk Toast Region prop.
| Prop | Type | Default | Description |
|---|---|---|---|
toaster | Toaster | the shared toast | The queue it shows. createToaster() makes another. |
side | "top" | "bottom" | "top" | The edge of the screen. |
align | "start" | "center" | "end" | end on macOS, center on touch | Where along that edge. |
maxVisible | number | 3 | How many show at once; the rest wait. |
expanded | boolean | — | Keeps the stack spread out. |
children | (toast, item) => ReactNode | — | Renders each notification yourself, with Toast and its parts. |
toast(title, options)
| Prop | Type | Default | Description |
|---|---|---|---|
description | ReactNode | — | More detail under the title. |
data | { icon?: ReactNode; time?: ReactNode } | — | The app icon and the time shown at the trailing edge. |
action | { label, onAction } | — | One button, like Undo. |
duration | number | 5000 | Milliseconds it shows. Infinity keeps it until dismissed. |
priority | "polite" | "assertive" | — | How screen readers announce it. |
toast.success, .info, .warning, .error, .loading, .promise, .update and .dismiss come from ui-sdk. For your own layout, Toast, ToastIcon, ToastTitle, ToastDescription, ToastAction and ToastClose are exported with macbetter’s classes.
| Data attribute | On | Meaning |
|---|---|---|
data-side, data-align | .mb-toaster | Where the stack sits |
data-expanded | .mb-toaster, .mb-toast | The stack is spread out |
data-front | .mb-toast | The newest notification |
data-type | .mb-toast, .mb-toast-icon | default, success, error, info, warning or loading |
data-swiping, data-swipe-direction | .mb-toast | Being swiped, and which way it left |
| CSS variable | Default |
|---|---|
--mb-toast-width | 344px on macOS, 400px on touch |
--mb-toast-radius | --mb-popup-radius on macOS, --mb-radius-2xl on touch |
--mb-toast-icon-size | 32px on macOS, 38px on touch |
--mb-toast-peek | How far each card behind the front one shows |