<!-- Source: README.md -->

# @codenhub/toaster

Instance-based browser toasts and native interactive dialogs with isolated DOM, configuration, queues, and token styles.

> [!WARNING] This package is experimental. Its API, rendering behavior, CSS, and support level may change before a stable release.

## Installation

```sh
pnpm add @codenhub/toaster
```

## Usage

Import the required stylesheet once in the browser entrypoint.

```ts
import { createToaster } from "@codenhub/toaster";
import "@codenhub/toaster/styles";

const toaster = createToaster();
const saving = toaster.loading.show({ message: "Saving..." });

await saveChanges();
saving.dismiss();
toaster.semantic.success("Changes saved");

// Release DOM, timers, listeners, dialogs, and token styles on teardown.
toaster.destroy();
```

## Documentation

- [Documentation overview](docs/index.md)
- [API, CSS, and lifecycle reference](docs/reference.md)
- [Accessibility and custom content](docs/accessibility-and-content.md)

## Requirements

- Rendering requires a browser DOM and `@codenhub/toaster/styles`.
- Interactive APIs require native `<dialog>` support. Construction is SSR-safe, but rendering operations throw without a document.
- `@codenhub/styles >=0.3.0` is an optional peer, and consumers do not need Tailwind configuration either way. Every toast and dialog color composes live against `@codenhub/styles`' own tokens — `--color-<intent>`, the presentation axis (`--ui-fill`/`--ui-border`), and the aesthetic axis (`--ui-clip`, `--ui-shadow-*`, `--ui-elevation`) — using the same fill/edge formula its own components run, so a runtime change to a `--color-*` token or an active aesthetic class reaches an already-rendered toast the same way it reaches a `.alert` or a `.btn`. When `@codenhub/styles` isn't installed, the identical formula runs against this package's own generated, uncomposed color inputs (`generated-defaults.css`), so standalone rendering stays usable with nothing left to drift from the real thing. The toast body composes like `.alert`'s own unstyled default (a soft, edged surface grounded on the page); the dialog's action buttons compose like `.btn`'s (a solid, edgeless, raised control).
- Presentation (`.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless`) works the same way it does on a real styles component: declared as a plain, inheriting custom-property class, so one on the toast or dialog element itself always wins over one only an ancestor carries. A consumer's own `ToastTokens` color always wins over the composed result for the property it sets.
- A restrictive `style-src` Content Security Policy that blocks unnonced inline styles can supply `ToasterConfig.nonce`, applied to the `<style>` element instance color tokens are written through.
- If also using `@codenhub/styles`' classless native-element mappings (`@codenhub/styles/native` or `/tw/native`), import them before `@codenhub/toaster/styles`: that stylesheet resets every bare `<button>` to the neutral intent at the same zero specificity toaster's dialog buttons use, and import order decides which wins.
- Dark-mode styling activates under a `.dark` class or `data-theme="dark"` attribute on an ancestor (usually `<html>`) — the same DOM contract `@codenhub/theme` produces. Toaster has no dependency on `@codenhub/theme`; any mechanism that sets those attributes works. Theme resolution is nested: a `.light`/`.dark` section inside an oppositely-themed ancestor resolves its own colors correctly, standalone or not.

## Notes

Configuration, messages, durations, positions, and color tokens are validated and may throw synchronously. Calls after `destroy()` throw.

## License

Licensed under Apache-2.0.

Bundled Lucide SVG icons use the ISC License. See [NOTICE](NOTICE).

<!-- Source: docs/index.md -->

# Show toasts and dialogs

`@codenhub/toaster` creates isolated managers for semantic, loading, and custom browser toasts plus native confirm, prompt, and alert dialogs. Each manager owns its stacks, queues, DOM, timers, listeners, and token stylesheet.

## Setup

### Installation

```sh
pnpm add @codenhub/toaster
```

### Quick start

Import the required global stylesheet once in the browser entrypoint:

```ts
import { createToaster } from "@codenhub/toaster";
import "@codenhub/toaster/styles";

const toaster = createToaster();
const saving = toaster.loading.show({ message: "Saving..." });

await saveChanges();
saving.dismiss();
toaster.semantic.success("Changes saved");

// Release DOM, timers, listeners, dialogs, and token styles on teardown.
toaster.destroy();
```

Retain handles when a toast must be updated or dismissed. Call `destroy()` when the owning application scope is torn down; later calls on that manager throw.

### Configuration

Pass `ToasterConfig` to `createToaster()` to set position, visible capacity, duration, margins, category defaults, color tokens, or a fixed container. Runtime changes use `configure()`, but the container cannot change. Configuration values are validated and invalid values throw synchronously.

## Requirements

- Rendering requires a browser DOM and `@codenhub/toaster/styles`.
- Construction is SSR-safe unless initial tokens need the DOM. Rendering and DOM-dependent configuration throw without a document.
- Interactive APIs require native `<dialog>` support. No polyfill or non-modal fallback is included.
- `@codenhub/styles >=0.3.0` is an optional peer, and consumers do not need Tailwind configuration either way. Every toast and dialog color composes live against `@codenhub/styles`' own tokens — `--color-<intent>`, the presentation axis (`--ui-fill`/`--ui-border`), and the aesthetic axis (`--ui-clip`, `--ui-shadow-*`, `--ui-elevation`) — using the same fill/edge formula its own components run, so a runtime change to a `--color-*` token or an active aesthetic class reaches an already-rendered toast the same way it reaches a `.alert` or a `.btn`. When `@codenhub/styles` isn't installed, the identical formula runs against this package's own generated, uncomposed color inputs (`generated-defaults.css`), so standalone rendering stays usable with nothing left to drift from the real thing. The toast body composes like `.alert`'s own unstyled default (a soft, edged surface grounded on the page); the dialog's action buttons compose like `.btn`'s (a solid, edgeless, raised control).
- Presentation (`.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless`) works the same way it does on a real styles component: declared as a plain, inheriting custom-property class, so one on the toast or dialog element itself always wins over one only an ancestor carries. A consumer's own `ToastTokens` color always wins over the composed result for the property it sets.
- A restrictive `style-src` Content Security Policy that blocks unnonced inline styles can supply `ToasterConfig.nonce`, applied to the `<style>` element instance color tokens are written through.
- If also using `@codenhub/styles`' classless native-element mappings (`@codenhub/styles/native` or `/tw/native`), import them before `@codenhub/toaster/styles`: that stylesheet resets every bare `<button>` to the neutral intent at the same zero specificity toaster's dialog buttons use, and import order decides which wins.
- Dark-mode styling activates under a `.dark` class or `data-theme="dark"` attribute on an ancestor (usually `<html>`) — the same DOM contract `@codenhub/theme` produces. Toaster has no dependency on `@codenhub/theme`; any mechanism that sets those attributes works. Theme resolution is nested: a `.light`/`.dark` section inside an oppositely-themed ancestor resolves its own colors correctly, standalone or not.

## Next steps

- [API, CSS, and lifecycle reference](docs/reference.md): Complete entrypoints, configuration, dispatchers, handles, queueing, validation, SSR, and cleanup.
- [Accessibility and custom content](docs/accessibility-and-content.md): Roles, message guidance, dialogs, reduced motion, focus, trusted DOM nodes, and string sanitization.

<!-- Source: docs/accessibility-and-content.md -->

# Accessible and custom content

## Toast Announcements

Semantic defaults use `status` for success/info and `alert` for error/warning; loading uses `status`. Consumers can override roles where options permit. The package supplies live-region attributes and an accessible dismiss label, "Dismiss toast" by default; `ToasterConfig.labels.dismiss` overrides it at the instance level for localization, applying to every toast that instance dispatches. The live-region role is inserted into the document empty before its message content is added, matching the [ARIA22 technique](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA22.html), so assistive technology tracking the region is watching before the content mutation happens rather than missing it. Consumers remain responsible for concise meaningful messages, appropriate urgency, enough reading/dismissal time, keyboard alternatives, and token color contrast.

## Native Dialogs

Interactive APIs use native modal `<dialog>` behavior for top-layer modality and focus containment. The package associates title/message text, sets initial focus, and restores prior focus when possible. Consumers must provide meaningful labels and verify browser support; no dialog polyfill or non-modal fallback is included. Reduced-motion preference skips transition waits. `ToasterConfig.labels` (`confirm`/`cancel`/`submit`/`ok`) sets instance-level default button labels so a consumer localizing dialogs does not have to repeat the same option on every `confirm`/`prompt`/`alert` call; a per-call label still wins. A dialog's own rendered content -- including a prompt's draft input value -- is cleared as soon as it closes, not left sitting until the dialog is reused for the next call.

Dialog title, message, labels, placeholder, and normal toast messages are inserted as text rather than HTML.

## Custom Content Trust Boundary

`ToastContent` accepts a string, a DOM `Node`, or a function returning either. Strings are parsed and sanitized. Application-supplied nodes are trusted and inserted without sanitization; consumers own their semantics, accessible names, event cleanup, and security.

A string is parsed through `Element.innerHTML`, a Trusted-Types-guarded sink. Under a host `require-trusted-types-for 'script'` Content Security Policy with no default policy, a consumer using the custom-string path must supply their own Trusted Types policy; the package does not install one on the consumer's behalf. Built-in semantic/loading content and the dismiss control are unaffected by that policy: they are constructed with DOM APIs rather than parsed markup and need no policy of their own.

Sanitized strings allow `a`, `b`, `br`, `code`, `div`, `em`, headings, `i`, lists, `p`, `pre`, `span`, and `strong`. Only `href`, `target`, and `rel` attributes survive. Explicit URL protocols are limited to `http`, `https`, `mailto`, and `tel`; relative, fragment, and protocol-relative links remain. Targets are limited to `_blank` and `_self`, and `_blank` receives `noopener noreferrer`. Script, style, iframe, object, and embed elements are removed with their content; other unsupported wrappers are unwrapped after their descendants are sanitized. A string that sanitizes down to nothing renderable -- a script-only string, for example -- throws the same "must not be empty" error a literally empty string would, rather than rendering a blank toast.

Use text messages for untrusted plain content. Use custom HTML only when the allowed formatting and link behavior are needed. Treat a `Node` or node factory as an explicit trusted-code boundary.

<!-- Source: docs/reference.md -->

# API, CSS, and lifecycle

## Public Entrypoints

- `@codenhub/toaster` exports `createToaster` and all public types.
- `@codenhub/toaster/styles` exports required prebuilt CSS for layout, variants, dialogs, animation, and responsive behavior.

Import the stylesheet once. The optional `@codenhub/styles >=0.3.0` peer can supply shared variables; this package's own generated defaults allow standalone use. No Tailwind consumer configuration is required. When `@codenhub/styles` is present, every radius and border-width reads its base token first (`--radius-control`, `--radius-surface`, `--border-width`) — matching `box.css`/`surface.css`'s own fallback chain — before falling back further to toaster's own fixed values when `@codenhub/styles` isn't installed at all. An active aesthetic (e.g. `.glass`, `.neobrutalism`) layers its material tokens (`--ui-radius`, `--ui-border-width`, `--ui-surface-shadow`, `--ui-clip`, `--ui-elevation`) on top of that base, generically: toaster's CSS reads the same `--ui-*` custom properties any real styles component reads, so it needs no per-aesthetic code to pick them up.

Every color composes live, the same formula `@codenhub/styles`' own `box.css` runs: an intent input (`--color-<name>`/`-contrast`/`-strong`, live when `@codenhub/styles` is present) is mixed against the presentation axis (`--ui-fill`, capped by the intent's own fill ceiling) to produce a background, with the edge and on-fill contrast derived the same way `box.css` derives them. A runtime change to a `--color-*` token, or to the presentation/aesthetic classes in scope, reaches an already-rendered toast or dialog button the same way it reaches a real `.alert` or `.btn`. When `@codenhub/styles` isn't installed, the identical formula runs against this package's own generated, uncomposed inputs (`generated-defaults.css`, produced by `scripts/generate-toast-defaults.mjs` from `@codenhub/styles`' real palette at generate time), so standalone rendering stays close to identical with nothing hand-typed to drift from the real thing.

The toast body composes like `.alert`'s own unstyled default: a soft (12%), edged surface grounded on the page background, no elevation. The dialog's action buttons compose like `.btn`'s own unstyled default instead: solid (100%), edgeless, raised (elevation 1), grounded on transparent. Neither is toaster's own choice to make up; both mirror the same registry defaults `.alert` and `.btn` themselves declare.

Presentation (`.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless`) is a plain, inheriting custom-property class matching `@codenhub/styles`' own `presentation.css` exactly (`.solid` sets `--ui-fill: 100%`, and so on) — restated as a standalone shim so it also works with no `@codenhub/styles` on the page. Because it is an ordinary custom property, a class on the toast or dialog element itself always wins over one only an ancestor carries, the same rule any real styles component follows, and arbitrary `--ui-fill`/`--ui-border` percentages set directly (not only these five named classes) are honored too. A consumer's own `ToastTokens` color always wins over the composed result for the property it sets. The dialog container and its cancel button carry no severity of their own, so they read a flat, live-first surface/text/border token instead of running the intent formula. The dialog's action buttons additionally each expose their own edge token (`primaryEdge`/`secondaryEdge`/`successBtnEdge`/`destructiveBtnEdge`) that wins over the composed edge under `.edged`, matching the toast body's own per-severity edge tokens; the cancel button gets the same `.edged` treatment via the flat border token. Two further surfaces opt out for legibility: the dialog's text input keeps its border regardless of `.edgeless` and its background stays transparent regardless of presentation, and its `:focus-visible` ring reuses the primary action button's own color chain rather than a hand-picked literal. Toast entrance/exit animation duration and easing read `--motion-duration-slow`/`--motion-ease` live too, falling back to this package's own default timing when unset.

The dialog's action buttons render as native `<button>` elements. If a consumer also loads `@codenhub/styles`' classless native-element mappings (`@codenhub/styles/native` or `@codenhub/styles/tw/native`), that stylesheet resets every bare `button` to the neutral intent at zero specificity — the same specificity toaster's own dialog-button rules use. Import order decides which wins: import the native mappings before `@codenhub/toaster/styles`, not after, or a dialog button's intent styling is silently reset to neutral.

Dark-mode styling activates under a `.dark` class or `data-theme="dark"` attribute on an ancestor (usually `<html>`) — the same DOM contract `@codenhub/theme` produces. Toaster has no dependency on `@codenhub/theme`; any mechanism that sets those attributes works. Because `@codenhub/styles`' color tokens resolve through `light-dark()` against the consuming element's own computed `color-scheme`, and this package's own generated defaults resolve the same way, a `.light`/`.dark` section nested inside an oppositely-themed ancestor resolves its own toasts and dialogs correctly, not just the document root.

A restrictive `style-src` Content Security Policy that blocks an unnonced inline `<style>` element can supply `ToasterConfig.nonce`; it is applied to the `<style>` element instance color tokens (`ToasterConfig.tokens`) are written through. A stylesheet that fails to initialize under CSP throws and leaves no owned node behind.

## Create and Configure

`createToaster(config?): Toaster` creates an independent instance. Defaults are position `"top-right"`, `maxVisible: 5`, duration `4000`, no dismiss button, and auto-dismiss enabled. `ToasterConfig` also accepts a fixed `container`, color `tokens`, a CSP `nonce` for the token stylesheet, margin, `className`, instance-level `labels`, and semantic/loading/custom category defaults. `ToasterRuntimeConfig` is the same partial shape without `container`.

`ToastLabels` sets instance-level default text for the dismiss button (`dismiss`) and the dialog action buttons (`confirm`, `cancel`, `submit`, `ok`) -- localization without repeating the same option on every `confirm`/`prompt`/`alert` call. A per-call label (`ConfirmOptions.confirmLabel`, etc.) still wins over its matching `labels` entry, the same precedence `position` and `duration` already have between `ToasterConfig` and a per-call option; an unset field falls back to this package's own built-in English text.

Margin is a property of the shared stack at a position, not of an individual toast: the most recently dispatched explicit value applies to every toast already showing at that position, and a dispatch that omits `margin` leaves the stack's current margin unchanged. Its own default (used only while nothing has set an explicit value) floors at `env(safe-area-inset-*)`, so a corner stack does not render underneath a device notch or home indicator; an explicit `margin` always wins outright and is not combined with the inset.

Unlike `position` and `duration`, a config-level `className` is not overridden by a per-call `className` — the two are appended (config first, then the call's own class) on every toast and every interactive dialog dispatched from that instance.

Construction validates positions, finite non-negative durations, positive integer capacity, and CSS color tokens. Invalid values throw. `configure()` validates runtime changes; attempting to change `container` throws. Configuring tokens or margins needs a DOM.

`Toaster` exposes `semantic`, `loading`, `interactive`, and `custom` dispatchers. `clear()` dismisses non-interactive categories only. `destroy()` is idempotent; it dismisses active/queued work, closes dialogs, restores focus when possible, removes owned DOM/listeners/timers/styles, and makes later calls throw. Dismissal through `destroy()` is immediate and synchronous: every handle settles (`state` becomes `"hidden"` and `settled` resolves) before `destroy()` returns, canceling any in-flight entrance or exit animation rather than waiting for one that nothing is left to see complete on its own -- unlike a normal `dismiss()`/`hide()`, which still animates.

Each instance owns a fully separate stack, dialog controller, and DOM, with no coordination between instances: two instances configured to the same position render two independent, potentially overlapping stacks, and two instances can each open a native dialog at the same time (each is a real top-layer modal, so both are visible and independently focusable). Reaching for two `Toaster` instances in the same app -- rather than one, configured per dispatch with a per-call `position`/`container` override -- is what produces this; keeping them at different positions/containers avoids the overlap, and there is no dedicated cross-instance API for it. A toast rendered outside the top layer is subject to the platform's own modal semantics while any dialog (from the same instance or another) is open with `showModal()`: the browser makes the rest of the document inert, so a background toast's dismiss button stops being clickable and assistive technology stops reaching it, resuming once every open dialog closes. This is standard `<dialog>` behavior, not something toaster adds or can opt out of.

## Dispatchers

`SemanticDispatcher` provides `show()` and `success()`, `error()`, `warning()`, and `info()` helpers plus category `clear()`. `SemanticType` contains those four variants. Success/info default to role `status`; error/warning default to `alert`.

`LoadingDispatcher.show(LoadingToastOptions)` creates a `status` toast that does not auto-dismiss; `clear()` dismisses loading toasts. `CustomDispatcher.show()` accepts `CustomToastOptions`; its `clear()` affects only custom toasts.

`InteractiveDispatcher` exposes `confirm`, `prompt`, and `alert`. Calls require a non-empty message and queue FIFO behind one native `<dialog>`. Their `ConfirmOptions`, `PromptOptions`, and `AlertOptions` configure labels, title, backdrop dismissal (default true), action type, classes, and tokens. Prompt adds default value and placeholder.

Queued/visible dismissal resolves confirm as `false`, prompt as `null`, and alert as `undefined`. Native dialog setup failures reject `result`, clean up, and allow the next queued dialog to run.

## Handles and Queueing

`ToastHandle` provides `dismiss()`, `update(ToastUpdateOptions)`, `settled`, `state`, and `onShow`/`onShown`/`onHide`/`onHidden` subscriptions. Lifecycle subscriptions return unsubscribe functions; late registration after an event runs immediately. `update()` can replace the message or content, swap the icon, switch the severity `type` (recoloring the toast and updating its role to match -- e.g. turning a loading toast into a success toast with `update({ type: "success" })`), change `duration` or `shouldAutoDismiss`, and replace tokens or consumer classes; sent to a visible toast, it applies immediately, and sent to a still-queued one, it is stored and applied once a slot opens rather than discarded. `content` supersedes `message` and `icon` in the same call, the same mutual exclusivity construction itself applies; when `type` is given without its own `icon`, the icon matching that type is used automatically. `update()` has no effect once the toast has settled, and throws under the same conditions construction does: an invalid token color, a negative/non-finite `duration`, an unrecognized `type`, or `content` that resolves to nothing renderable.

`InteractiveToastHandle<T>` provides `dismiss()`, `settled`, `state`, and `result`. `ToastState` is `"queued" | "visible" | "hiding" | "hidden"`.

At capacity, a new non-interactive toast queues and the oldest _eligible_ active toast is dismissed to make room; admission is FIFO. A toast that is a loader, hovered, or focused is protected and never evicted for this — if every active toast is protected, the new one simply waits queued instead. Lowering `maxVisible` at runtime through `configure()` reconciles existing occupancy the same way: eligible toasts are dismissed down to the new limit, but a limit lowered below the number of currently protected toasts is not enforced by evicting one of them, and any newly-freed slots admit queued work immediately. Queued toasts can be dismissed before rendering. Auto-dismiss starts after entrance, pauses for hover, focus, or the document's tab being in the background (`document.visibilityState === "hidden"`), and resumes with the remaining duration once every pausing condition clears. `settled` resolves after removal and cleanup.

If more simultaneous toasts are showing at one position than fit in the viewport (a short viewport, several toasts, or tall content can each produce this), the stack scrolls internally instead of running off-screen with no way back; a newly dispatched toast always scrolls into view on arrival, and dismissing one leaves the stack's current scroll position alone rather than fighting a consumer who scrolled to read an older one. The stack's own scrollbar chrome is hidden, since a platform scrollbar track next to a floating, rounded toast reads as a rendering artifact rather than a real scrollable panel; wheel, touch, and the scroll-to-reveal behavior above all still work. A toast anchored to `top-right`/`bottom-right` stays flush against that physical edge regardless of how wide its siblings are, instead of drifting toward the opposite edge as a wider sibling grows the stack. Dismissing a toast whose own element currently holds keyboard focus (e.g. its dismiss button) restores focus to whatever held it immediately before that toast last rendered, if that element still exists — the same restoration `InteractiveDispatcher` dialogs already do.

## Options and Tokens

`SemanticToastOptions`, `LoadingToastOptions`, and `CustomToastOptions` provide their required message/content and applicable `ToastPosition`, duration, dismissal, `ToastRole`, margin, class, and token fields. Messages and string content must be non-empty; durations must be finite and at least zero. Category fallback types are `SemanticDefaults`, `LoadingDefaults`, and `CustomDefaults`.

`ToastPosition` supports corners, top/bottom center, and center. `ToastTokens` exposes optional colors for semantic variants (including their edge/border), surface/border/text, and dialog action fill, hover, and edge/border states. Values are validated with `CSS.supports` when available and declaration delimiters are always rejected. Instance tokens use an owned scoped stylesheet; per-toast/dialog tokens stay element-scoped.

## SSR and Browser Lifecycle

Construction is SSR-safe unless it must apply initial tokens. Rendering, interactive calls, and DOM-dependent reconfiguration require a document and throw without one. The configured container determines the owner document and cannot change. Interactive APIs require native `HTMLDialogElement.showModal()` and `close()`; prompt focus requires `requestAnimationFrame`, and listener cleanup uses `AbortController`.

Animations use the Web Animations API when available and complete immediately when it is absent or throws. `matchMedia`, when available, disables motion for `prefers-reduced-motion: reduce`. A canceled animation (entrance or exit) completes the same lifecycle step a finished one would, rather than leaving the toast stuck mid-transition.

Built-in icons and the dismiss control are constructed with DOM APIs rather than parsed from a markup string, so they render under a host `require-trusted-types-for 'script'` Content Security Policy with no default policy configured. A rendering failure — including one a host security policy causes — releases the toast's reserved stack slot and reports the error rather than leaving the slot permanently unavailable. The custom-content string path (see [Accessibility and custom content](docs/accessibility-and-content.md)) still parses markup and needs its own Trusted Types policy under that same enforcement.

## Public Exports

The root exports `createToaster`; `Toaster`, `SemanticDispatcher`, `LoadingDispatcher`, `InteractiveDispatcher`, `CustomDispatcher`, `ToasterConfig`, `ToasterRuntimeConfig`, `ToastHandle`, `InteractiveToastHandle`, `ToastUpdateOptions`, `ToastState`, `ToastPosition`, `ToastRole`, `ToastLifecycleSubscriber`, `ToastTokens`, `ToastLabels`, `SemanticToastOptions`, `SemanticType`, `LoadingToastOptions`, `CustomToastOptions`, `ConfirmOptions`, `PromptOptions`, `AlertOptions`, `SemanticDefaults`, `LoadingDefaults`, `CustomDefaults`, and `ToastContent` are public types.

<!-- Source: docs/changelog/0.1.0.md -->

# 0.1.0

First publish.

## Added

- `createToaster()` returns an independent instance with its own DOM, queues, timers, listeners, and token stylesheet.
- Semantic (`success`/`error`/`warning`/`info`), loading, and custom-content toast dispatchers, plus native `confirm`/`prompt`/`alert` dialogs, each returning a promise-based result.
- Overflow admission queues FIFO at capacity; a loader, a hovered toast, or a focused toast is protected from eviction, and a `maxVisible` lowered at runtime reconciles existing occupancy the same way.
- Toast and dialog colors, presentation (`.solid`/`.soft`/`.ghost`/`.edged`/`.edgeless`), and aesthetic material (clip, shadow, elevation) compose live against an optional `@codenhub/styles >=0.3.0` peer, with a generated standalone fallback when it isn't installed.
- Per-instance and per-call `ToastTokens` color overrides, an optional CSP `nonce` for the token stylesheet, and a sanitized custom-HTML content path alongside trusted DOM node content.
- `ToastHandle.update()` can also replace a toast's message/content, swap its icon, switch its severity `type` (e.g. turning a loading toast into a success toast), and change its duration or auto-dismiss setting -- not only tokens and classes.
- `ToasterConfig.labels` sets instance-level default text for the dismiss button and dialog buttons (confirm/cancel/submit/ok), for localization without repeating the same option on every dialog call; a per-call label still wins over it.
- Auto-dismiss now pauses while the browser tab is in the background, the same way hover and focus already pause it, so time nobody could have read the toast doesn't count against its duration.

## Fixed

- `destroy()` now settles every handle synchronously instead of leaving it in `"hiding"` until an exit animation nothing is left to see would have finished.
- A closed dialog's rendered content (e.g. a prompt's draft input value) is cleared immediately instead of lingering until the dialog is reused.
- Custom content that sanitization strips down to nothing (e.g. a script-only string) now throws the same "must not be empty" error a literally empty string would, instead of silently rendering a blank toast.
- The dismiss button uses logical CSS properties so it sits at the correct trailing edge in right-to-left layouts instead of always the physical right.
- A corner stack's default margin now floors at `env(safe-area-inset-*)`, so it no longer renders underneath a device notch or home indicator when no explicit `margin` is configured. The stack's own scroll cap (`max-height`) now reserves the same, independently-computed top and bottom insets instead of doubling one of them, so a tall stack can no longer grow into whichever edge's inset the doubled value didn't account for.
- A stack with more simultaneous toasts than fit the viewport (a short viewport, several toasts, or tall content) now scrolls internally instead of running off-screen with the extra toasts permanently unreachable.
- A toast's width no longer floors or ceilings past what a large configured `margin` leaves available on a narrow viewport; it shrinks (wrapping its text) instead of overflowing past the margin.
- Dismissing a toast whose own element currently holds keyboard focus now restores focus to whatever held it immediately before that toast rendered, instead of dropping it to `<body>`.
- The scrollable stack no longer renders its own platform scrollbar track next to the toasts; scrolling (wheel, touch, and the scroll-to-reveal-newest behavior) still works with the chrome hidden.
- A toast anchored to a right-aligned position (`top-right`/`bottom-right`) now stays flush against that edge instead of drifting toward the left as a wider sibling joins the stack and grows its width.
- Adding a toast to a stack that already has one visible no longer clips or snaps an existing toast partway through the push animation. Two contributing causes were fixed: the push now accounts for the new toast's real, final height instead of the still-empty shell it briefly has before its content is populated, and the stack's scroll position is settled before that push is computed rather than after.
