Toast and Sonner
Toast and Sonner: anatomy, keyboard behavior, state, and theming in Askr.
Example
import { Toast, ToastDescription, ToastHost, ToastTitle } from '@askrjs/themes/components';
<ToastHost>
<Toast open={saved()} onOpenChange={setSaved}>
<ToastTitle>Project saved</ToastTitle>
<ToastDescription>Your changes are now visible.</ToastDescription>
</Toast>
</ToastHost>Dismiss transient feedback from the viewport.
Published props
Generated from the TypeScript declarations shipped by the installed package. Named types in the Type column define the accepted values.
ToastActionAsChildProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
type | type?: undefined; | — | — |
ToastActionProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
type | type?: "button" | "submit" | "reset" | undefined; | — | — |
ToastCloseAsChildProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<Element>; | — | — |
type | type?: undefined; | — | — |
ToastCloseProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
disabled | disabled?: boolean | undefined; | — | — |
onPress | onPress?: ((event: PressEvent) => void) | undefined; | — | — |
ref | ref?: Ref<HTMLButtonElement>; | — | — |
type | type?: "button" | "submit" | "reset" | undefined; | — | — |
ToastDescriptionAsChildProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
ToastDescriptionProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ToastHostOwnProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
duration | duration?: number | undefined; | — | — |
id | id?: string | undefined; | — | — |
ToastHostProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
duration | duration?: number | undefined; | — | — |
id | id?: string | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ToastOwnProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
children | children?: unknown; | — | — |
defaultOpen | defaultOpen?: boolean | undefined; | — | — |
duration | duration?: number | undefined; | — | — |
id | id?: string | undefined; | — | — |
onOpenChange | onOpenChange?: ((open: boolean) => void) | undefined; | — | — |
open | open?: boolean | undefined; | — | — |
variant | variant?: ToastVariant | undefined; | — | — |
ToastProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
defaultOpen | defaultOpen?: boolean | undefined; | — | — |
duration | duration?: number | undefined; | — | — |
id | id?: string | undefined; | — | — |
onOpenChange | onOpenChange?: ((open: boolean) => void) | undefined; | — | — |
open | open?: boolean | undefined; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
variant | variant?: ToastVariant | undefined; | — | — |
ToastTitleAsChildProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
ToastTitleProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
ToastViewportAsChildProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild: true; | — | — |
children | children: JSXElement; | — | — |
ref | ref?: Ref<Element>; | — | — |
ToastViewportProps
Import from @askrjs/ui/toast.
| Prop | Type | Default | Description |
|---|---|---|---|
asChild | asChild?: false | undefined; | — | — |
children | children?: unknown; | — | — |
ref | ref?: Ref<HTMLDivElement>; | — | — |
Purpose
Toast is the headless notification primitive: a host component that owns a registry of active entries, plus the individual pieces you compose into each one — this is the real, working notification system. Sonner (aliased from `Toaster`) is not a drop-in replacement for it: it's a bare presentational wrapper with no imperative `toast()` API, queueing, stacking, timing, or dismiss logic of its own — despite sharing a name with the popular standalone Sonner library, it doesn't reproduce that library's behavior. If you need an actual working notification system today, build it on Toast/ToastHost, not Sonner.
Install and import
Headless pieces — ToastHost, ToastViewport, Toast, ToastTitle, ToastDescription, ToastAction, ToastClose — come from `@askrjs/ui/toast`. The themed layer re-exports that same set from `@askrjs/themes/toast`, and separately exposes `@askrjs/themes/sonner`, which is its own API with no headless counterpart to import instead.
Live examples
Trigger Toast from this page and inspect Sonner's markup separately — they're not offering the same behavior to compare side by side. Toast actually stacks, times out, and dismisses entries through ToastHost's registry; Sonner is a bare styled wrapper with none of that wired up, so there's no live stacking/timing/dismiss behavior to observe from it here.
Anatomy
ToastHost sits near the root of your tree and owns the registry plus a default `duration`; ToastViewport renders the live region the stack mounts into. Each notification is declared with a Toast, which returns no DOM of its own, composed from ToastTitle, ToastDescription, and optionally ToastAction or ToastClose for interactive controls. Sonner skips all of that composition — it's a single mounted piece, closer in spirit to the themed catalog's Toaster than to the ToastHost/Toast model.
State model
An individual Toast takes `open`/`defaultOpen`/`onOpenChange` like the other disclosure primitives, plus its own `duration` (falling back to ToastHost's default when omitted) and a `variant` of `'default' | 'success' | 'warning' | 'danger' | 'info'`. Setting `duration` on ToastHost changes that fallback for every toast underneath it that doesn't specify one itself.
Keyboard and accessibility
The stack renders with `role="status"` and `aria-live="polite"`, per TOAST_A11Y_CONTRACT, so new toasts get announced without stealing focus, and the viewport carries an accessible "Notifications" label out of the box. ToastAction and ToastClose are ordinary focusable buttons; ToastAction runs its handler and then closes the toast on its own, so there's no need to also wire a separate dismiss call after the action fires.
Styling and tokens
`data-slot`, `data-state`, and `data-disabled` drive the themed enter/exit transitions for the ToastHost/Toast family. Sonner is a distinct API with its own presentation, so don't assume its class names or data attributes line up with Toast's — write separate styles for each rather than sharing selectors across them.
API
ToastHostProps adds `duration` on top of standard div props; ToastProps adds `variant`, `open`/`defaultOpen`/`onOpenChange`, and `duration`. ToastAction and ToastClose use the same button-like prop shape as other interactive primitives in the library, including `asChild`.
Edge cases
Toast and Sonner solve the same problem with two different composition models, so mixing them in one application means running two separate notification stacks side by side — pick one per app rather than splitting notification types between them. Because ToastHost owns the registry, a Toast rendered without a ToastHost above it has nowhere to register into, so mount ToastHost once near your app root instead of per page.
Related pages
See Progress for status feedback with a determinate value instead of a transient message, and Alert, Badge, Empty, Skeleton, and Spinner for feedback that should stay on screen rather than time out.