@lattice-ui/toast Stable direction
import Toast
depends on core , layer , motion Toast is the primitive for transient, non-blocking notifications: saved-changes confirmations, reward pop-ups, connection warnings, and inline status messages. A Provider owns the queue — enqueuing, the visible-count limit, per-toast duration, and exit timing — so your component only renders the surface for each toast and reacts to actions.
Reach for Toast when messages should stack and expire on their own, stay capped to a few at a time, and animate out without you tracking timers by hand. You drive it imperatively with the useToast hook from anywhere inside the provider.
Import
import { Toast, useToast } from "@lattice-ui/toast";Anatomy
Provider and Viewport form the minimum system: the provider owns the queue and the viewport renders it. Viewport renders a default surface for every visible toast out of the box, so you only reach for Root, Title, Description, Action, and Close when you compose your own toast layout.
<Toast.Provider> <Toast.Viewport> <Toast.Root> <Toast.Title /> <Toast.Description /> <Toast.Action /> <Toast.Close /> </Toast.Root> </Toast.Viewport></Toast.Provider>| Part | Required | Responsibility |
|---|---|---|
Toast.Provider | yes | Owns the queue, default duration, visible cap, and exit timing; exposes useToast. |
Toast.Viewport | yes | Stacks visible toasts; renders a default surface per toast unless asChild. |
Toast.Root | no | A single toast surface that animates between visible and hidden. |
Toast.Title | no | The toast heading. |
Toast.Description | no | The toast supporting text. |
Toast.Action | no | A button that runs onAction when activated. |
Toast.Close | no | A button that runs onClose when activated. |
Example
A provider at the top of the UI plus a button that enqueues toasts. The default Viewport stacks each queued toast’s surface automatically, so you only choose where the stack sits.
import { Toast, useToast } from "@lattice-ui/toast";
function SaveButton() { const toast = useToast();
return ( <textbutton Size={UDim2.fromOffset(140, 38)} Text="Save layout" Event={{ Activated: () => toast.enqueue({ title: "Layout saved", description: "Your changes are live for everyone.", durationMs: 3000, }), }} /> );}
export function SaveStatusToasts() { return ( <Toast.Provider defaultDurationMs={4000} maxVisible={3}> <SaveButton />
<frame AnchorPoint={new Vector2(1, 1)} BackgroundTransparency={1} Position={UDim2.new(1, -16, 1, -16)} Size={UDim2.fromOffset(340, 320)} > <Toast.Viewport /> </frame> </Toast.Provider> );}To render a fully custom toast surface, set asChild on Viewport and map the queue yourself with useToast().visibleToasts, composing Toast.Root, Toast.Title, Toast.Action, and Toast.Close by hand. Wire each Toast.Close to remove(toast.id).
There is no open/onOpenChange on a toast. You add toasts by calling enqueue and remove them with remove; the provider decides what is visible and when each one leaves. Treat the queue as the source of truth, not local component state.
How it behaves
The queue
Toast.Provider keeps an ordered queue of records. enqueue(options) appends a toast and returns its id (auto-generated as toast-N unless you pass id). remove(id) starts a toast’s exit, and clear() empties the queue immediately. The provider runs a RunService.Heartbeat connection while any toasts exist, pruning expired ones each frame, and disconnects once the queue drains.
Visibility limit
maxVisible (default 3) caps how many toasts are rendered at once; the rest wait in the queue. useToast().visibleToasts is the capped slice the viewport renders, while toasts is the full queue. Toasts beyond the cap do not start their duration countdown until they enter the visible window.
Duration and expiry
Each toast expires after its own durationMs, falling back to the provider’s defaultDurationMs (default 4000). A duration of 0 or less makes a toast sticky — it never expires on its own and must be removed via remove, Toast.Close, or clear. When a visible toast’s time elapses, the provider marks it exiting and keeps it around for a short exit window (~160 ms) so its motion can finish before it is dropped.
Motion and presence
Toast.Root animates its background between an active (opaque) and inactive (transparent) state based on visible, using a default response recipe. The default Viewport wires visible to each toast’s non-exiting state, so toasts fade as they leave. Override the recipe per root with transition.
Mount Toast.Provider high in your UI tree, above everything that calls useToast. useToast reads the provider through context and throws if used outside it. The Viewport does not have to be a direct child — it only needs to be somewhere inside the same provider.
API reference
Toast.Provider
| Prop | Type | Description |
|---|---|---|
| defaultDurationMs | number | Fallback lifetime for toasts without their own durationMs. Clamped to >= 0. Defaults to 4000. |
| maxVisible | number | Maximum toasts rendered at once; the rest queue. Clamped to >= 1. Defaults to 3. |
| children | React.ReactNode | The UI tree that enqueues toasts and renders the viewport. |
useToast
Returns the imperative API for the nearest provider.
| Prop | Type | Description |
|---|---|---|
| toasts | Array<ToastRecord> | The full queue, including toasts beyond the visible cap. |
| visibleToasts | Array<ToastRecord> | The capped slice currently eligible to render. |
| enqueue | (options: ToastOptions) => string | Adds a toast and returns its id. |
| remove | (id: string) => void | Starts the exit for the matching toast. |
| clear | () => void | Removes every toast immediately. |
ToastOptions accepts id, title, description, and durationMs — all optional.
Toast.Viewport
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Render the single child element instead of the default stacking frame and per-toast surfaces. You then map the queue yourself. |
| children | React.ReactNode | Extra content appended after the rendered toasts, or the single element when asChild is set. |
Toast.Root
| Prop | Type | Description |
|---|---|---|
| visible | boolean | Drives the active/inactive motion state. Defaults to true. |
| transition | MotionConfig | Overrides the default toast response recipe used for the show/hide animation. |
| asChild | boolean | Apply the visibility and motion ref to the single child element instead of the default frame. |
| children | React.ReactNode | The toast contents. |
Toast.Title
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Render the single child element instead of the default textlabel. |
| children | React.ReactElement | The element to render. Required when asChild is set. |
Toast.Description
| Prop | Type | Description |
|---|---|---|
| asChild | boolean | Render the single child element instead of the default textlabel. |
| children | React.ReactElement | The element to render. Required when asChild is set. |
Toast.Action
| Prop | Type | Description |
|---|---|---|
| onAction | () => void | Called when the action button is activated. |
| asChild | boolean | Merge the activation behavior onto the single child element instead of the default textbutton. |
| children | React.ReactElement | The element to render. Required when asChild is set. |
Toast.Close
| Prop | Type | Description |
|---|---|---|
| onClose | () => void | Called when the close button is activated. Wire this to remove(id). |
| asChild | boolean | Merge the activation behavior onto the single child element instead of the default textbutton. |
| children | React.ReactElement | The element to render. Required when asChild is set. |