# Toast and Sonner

> Toast and Sonner: anatomy, keyboard behavior, state, and theming in Askr.

Source: [https://askrjs.com/docs/components/toast-and-sonner](https://askrjs.com/docs/components/toast-and-sonner)

Status: limited. Packages: @askrjs/ui/toast, @askrjs/themes/toast, @askrjs/themes/sonner.

**Published packages are authoritative.** Examples may lag behind a published contract. When guidance differs, verify the exports and TypeScript declarations in your installed package, then file an issue.

## Example

```tsx
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>
```

## Published props

Generated from the TypeScript declarations shipped by the installed package.

### `ToastActionAsChildProps`

Import from `@askrjs/ui/toast`.

- `asChild: true;`
- `children: JSXElement;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<Element>;`
- `type?: undefined;`

### `ToastActionProps`

Import from `@askrjs/ui/toast`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<HTMLButtonElement>;`
- `type?: "button" | "submit" | "reset" | undefined;`

### `ToastCloseAsChildProps`

Import from `@askrjs/ui/toast`.

- `asChild: true;`
- `children: JSXElement;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<Element>;`
- `type?: undefined;`

### `ToastCloseProps`

Import from `@askrjs/ui/toast`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `disabled?: boolean | undefined;`
- `onPress?: ((event: PressEvent) => void) | undefined;`
- `ref?: Ref<HTMLButtonElement>;`
- `type?: "button" | "submit" | "reset" | undefined;`

### `ToastDescriptionAsChildProps`

Import from `@askrjs/ui/toast`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `ToastDescriptionProps`

Import from `@askrjs/ui/toast`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `ToastHostOwnProps`

Import from `@askrjs/ui/toast`.

- `children?: unknown;`
- `duration?: number | undefined;`
- `id?: string | undefined;`

### `ToastHostProps`

Import from `@askrjs/ui/toast`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `duration?: number | undefined;`
- `id?: string | undefined;`
- `ref?: Ref<HTMLDivElement>;`

### `ToastOwnProps`

Import from `@askrjs/ui/toast`.

- `children?: unknown;`
- `defaultOpen?: boolean | undefined;`
- `duration?: number | undefined;`
- `id?: string | undefined;`
- `onOpenChange?: ((open: boolean) => void) | undefined;`
- `open?: boolean | undefined;`
- `variant?: ToastVariant | undefined;`

### `ToastProps`

Import from `@askrjs/ui/toast`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `defaultOpen?: boolean | undefined;`
- `duration?: number | undefined;`
- `id?: string | undefined;`
- `onOpenChange?: ((open: boolean) => void) | undefined;`
- `open?: boolean | undefined;`
- `ref?: Ref<HTMLDivElement>;`
- `variant?: ToastVariant | undefined;`

### `ToastTitleAsChildProps`

Import from `@askrjs/ui/toast`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `ToastTitleProps`

Import from `@askrjs/ui/toast`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `ref?: Ref<HTMLDivElement>;`

### `ToastViewportAsChildProps`

Import from `@askrjs/ui/toast`.

- `asChild: true;`
- `children: JSXElement;`
- `ref?: Ref<Element>;`

### `ToastViewportProps`

Import from `@askrjs/ui/toast`.

- `asChild?: false | undefined;`
- `children?: unknown;`
- `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.

## Documentation navigation

[Previous](https://askrjs.com/docs/components/progress/index.md) | [Next](https://askrjs.com/docs/components/alert-badge-empty-skeleton-spinner-and-stat/index.md)
