Askr documentation
UI & Componentslimited

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>
Try Toast

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.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<Element>;
typetype?: undefined;

ToastActionProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<HTMLButtonElement>;
typetype?: "button" | "submit" | "reset" | undefined;

ToastCloseAsChildProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<Element>;
typetype?: undefined;

ToastCloseProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
disableddisabled?: boolean | undefined;
onPressonPress?: ((event: PressEvent) => void) | undefined;
refref?: Ref<HTMLButtonElement>;
typetype?: "button" | "submit" | "reset" | undefined;

ToastDescriptionAsChildProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

ToastDescriptionProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLDivElement>;

ToastHostOwnProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
childrenchildren?: unknown;
durationduration?: number | undefined;
idid?: string | undefined;

ToastHostProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
durationduration?: number | undefined;
idid?: string | undefined;
refref?: Ref<HTMLDivElement>;

ToastOwnProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
childrenchildren?: unknown;
defaultOpendefaultOpen?: boolean | undefined;
durationduration?: number | undefined;
idid?: string | undefined;
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;
openopen?: boolean | undefined;
variantvariant?: ToastVariant | undefined;

ToastProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
defaultOpendefaultOpen?: boolean | undefined;
durationduration?: number | undefined;
idid?: string | undefined;
onOpenChangeonOpenChange?: ((open: boolean) => void) | undefined;
openopen?: boolean | undefined;
refref?: Ref<HTMLDivElement>;
variantvariant?: ToastVariant | undefined;

ToastTitleAsChildProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

ToastTitleProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: Ref<HTMLDivElement>;

ToastViewportAsChildProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild: true;
childrenchildren: JSXElement;
refref?: Ref<Element>;

ToastViewportProps

Import from @askrjs/ui/toast.

PropTypeDefaultDescription
asChildasChild?: false | undefined;
childrenchildren?: unknown;
refref?: 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.

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.