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

# @codenhub/toaster

Instance-based browser toasts and native interactive dialogs with isolated DOM, configuration, queues, and CSS custom property 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 your client entrypoint. Use the pre-configured singletons for zero-boilerplate notifications and dialogs. Dialogs have their own entry point, so an app that only shows toasts does not bundle them:

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

// Neutral or semantic notifications
toast("File uploaded");
toast.success("Changes saved", {
  description: "Updated 4 files across 2 repositories.",
  action: {
    label: "Undo",
    onClick: (_event, handle) => {
      revertChanges();
      handle.dismiss();
    },
  },
});

// Promise lifecycle binding
await toast.promise(saveChanges(), {
  loading: "Saving changes...",
  success: "Changes saved",
  error: (err) => `Save failed: ${err instanceof Error ? err.message : String(err)}`,
});

// Interactive modal dialogs
if (await dialog.confirm("Delete workspace?")) {
  await deleteWorkspace();
}
```

For isolated DOM containers, micro-frontends, or custom instances, use `createToaster()`, and `createDialogs()` for dialogs:

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

const scopedToaster = createToaster({
  container: document.getElementById("modal-overlay")!,
  maxVisible: 3,
});

scopedToaster.success("Scoped notification");
const dialogs = createDialogs({ labels: { confirm: "Proceed" } });
await dialogs.confirm("Proceed?");
dialogs.destroy();
scopedToaster.destroy();
```

## Documentation

- [Documentation overview](docs/index.md): Entrypoint to all guides, architecture, and quick start.
- [Concepts](docs/concepts.md): Dual-layer architecture, lifecycle handles, and thenables.
- [Toasts guide](docs/toasts.md): Neutral, semantic, loading, structured, and promise notifications.
- [Dialogs guide](docs/dialogs.md): Confirmations, prompts, alerts, and label localization.
- [Styling guide](docs/styling.md): `--toast-color-*` color overrides, `@codenhub/styles` live composition, presentation classes, and CSP.
- [Accessibility and security](docs/accessibility.md): ARIA live regions, focus restoration, reduced motion, and custom content boundaries.
- [Scoped instances](docs/instances.md): Multi-instance coordination, custom containers, and lifecycle teardown.
- [API reference](docs/reference/index.md): Complete TypeScript symbol catalogue compiled from declarations.
- [Changelog](docs/changelog/index.md): Version history, breaking changes, and migration notes.

## Requirements

- Rendering requires a browser DOM and `@codenhub/toaster/styles`. Construction is SSR-safe, but rendering throws without a document.
- Dialogs require native `<dialog>` support.
- `@codenhub/styles >=0.3.0` is an optional peer. When present, toasts and dialogs compose live with its color, presentation, and aesthetic tokens; without it, the package's own defaults apply. See the [styling guide](docs/styling.md).
- If you use `@codenhub/styles`' native-element stylesheet, import it before `@codenhub/toaster/styles`. See [stylesheet ordering](docs/styling.md#classless-native-button-stylesheet-ordering).
- Dark mode follows a `.dark` class or `data-theme="dark"` attribute on an ancestor, as `@codenhub/theme` sets. See [dark mode](docs/styling.md#dark-mode-support).
- Colors are overridden with `--toast-color-*` CSS custom properties, set in your own CSS. The package injects no `<style>` element, so a strict `style-src` Content Security Policy needs no nonce. See [color overrides](docs/styling.md#color-overrides).

## Notes

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

## License

This project is licensed under the [Apache-2.0](LICENSE) license.

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

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

# Toaster documentation

`@codenhub/toaster` provides browser notifications and native interactive modal dialogs with zero-boilerplate singletons, isolated container instances, live `@codenhub/styles` token composition, and strict Content Security Policy compliance.

## Why Toaster?

Modern applications frequently balance transient notifications (build status, sync alerts) and synchronous user interruptions (confirming destructive actions, entering credentials). Most libraries compromise on either accessibility, styling flexibility, or memory safety.

`@codenhub/toaster` coordinates both feedback models through specialized browser primitives:

- **Dual-layer architecture**: Floating toast stacks render in light DOM containers with FLIP animations and FIFO eviction, while confirmation and prompt dialogs render into the browser's native top layer using HTML5 `<dialog>`.
- **Zero-boilerplate singletons**: Import `toast` from `@codenhub/toaster` and `dialog` from `@codenhub/toaster/dialog` for application-wide notifications and dialogs, or use `createToaster()` and `createDialogs()` for isolated instances. An app that only shows toasts does not bundle the dialog code.
- **First-class Promise workflows**: `toast.promise` coordinates pending, success, and error transitions with automatic timer re-arming; dialog methods return `DialogHandle<T>` implementing `PromiseLike<T>` so you can `await dialog.confirm(...)` directly.
- **Live token composition**: Toast colors, presentation axes (`.solid`, `.soft`, `.ghost`, `.edged`), and dark mode compose live with `@codenhub/styles` or fall back to standalone generated defaults.

## Setup

### Installation

Install the package using your package manager:

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

### Quick start

Import the required global stylesheet once in your client entrypoint, then use the pre-configured singletons anywhere in your application:

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

// 1. Plain neutral or semantic notifications
toast("Draft saved to cache");
toast.success("Profile updated");
toast.error("Failed to sync database");

// 2. Structured notification with action button
toast.success("Branch merged", {
  description: "Pull request #42 was merged into main.",
  action: {
    label: "Undo",
    onClick: (_event, handle) => {
      revertMerge();
      handle.dismiss();
    },
  },
});

// 3. Automated Promise lifecycle binding
await toast.promise(publishArticle(), {
  loading: "Publishing article...",
  success: "Article published live!",
  error: (err) => `Publication failed: ${err instanceof Error ? err.message : String(err)}`,
});

// 4. Interactive modal dialogs with native await
if (await dialog.confirm("Delete this workspace permanently?")) {
  await deleteWorkspace();
}
```

## Documentation guide

Explore the focused guides below to master every aspect of the package:

| Guide                               | Description                                                                                                               |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------------- |
| [Concepts](docs/concepts.md)             | The dual-layer mental model, lifecycle states (`queued`, `visible`, `hiding`, `hidden`), settled promises, and thenables. |
| [Toasts](docs/toasts.md)                 | Complete guide to dispatching neutral, semantic, loading, structured, and promise-driven notifications.                   |
| [Dialogs](docs/dialogs.md)               | Deep dive into native modal confirmations, prompts, alerts, thenable handles, and label localization.                     |
| [Styling](docs/styling.md)               | CSS custom property composition with `@codenhub/styles`, presentation classes, standalone fallback, dark mode, and CSP.   |
| [Accessibility](docs/accessibility.md)   | Screen reader announcements, native dialog focus trapping, reduced motion, and custom content boundaries.                 |
| [Scoped Instances](docs/instances.md)    | Creating isolated instances with `createToaster()`, custom DOM containers, micro-frontends, and SSR safety.               |
| [API Reference](docs/reference/index.md) | Complete TypeScript API catalogue generated automatically from package declarations.                                      |
| [Changelog](docs/changelog/index.md)     | Release history, breaking changes, and migration notes across versions.                                                   |

## Requirements and runtime compatibility

- **Browser DOM**: Rendering notifications and modal dialogs requires a browser DOM environment. Construction is SSR-safe, but invoking rendering methods in Node.js or SSR runtimes throws an error.
- **Native `<dialog>`**: Interactive dialogs require browser support for `HTMLDialogElement.showModal()`.
- **CSS Import**: `@codenhub/toaster/styles` must be loaded once in the application to render layout, positioning, and animation rules.
- **Styles Integration**: `@codenhub/styles >=0.3.0` is an optional peer dependency. When present, toaster colors and presentation axes compose live; when absent, toaster renders using built-in standalone palette defaults.
- **Content Security Policy**: The package injects no `<style>` element and uses no HTML-parsing sink, so a strict `style-src` or Trusted Types policy needs no nonce or policy for it.

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

# Mental model and concepts

Understanding how `@codenhub/toaster` coordinates notifications and dialogs helps you structure user feedback, prevent memory leaks, and design reliable asynchronous workflows.

## The two presentation layers

Modern web applications need two fundamentally different kinds of feedback: peripheral notifications that inform without interrupting, and modal interruptions that require an explicit decision before work can continue. `@codenhub/toaster` implements both using dedicated browser mechanisms:

| Surface                 | Presentation Layer                                    | Typical Use Case                                                            | Dismissal Behavior                                                  |
| ----------------------- | ----------------------------------------------------- | --------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| **Floating Toasts**     | Light DOM stack containers (`[data-toast-container]`) | Background job completion, copy confirmation, system alerts, promise states | Auto-dismiss timer, close button, or programmatic dismissal         |
| **Interactive Dialogs** | Browser Top Layer (`<dialog>` + `showModal()`)        | Destructive confirmations, user prompts, mandatory alerts                   | Explicit action button click, Escape key, or programmatic dismissal |

Because modal dialogs use the platform's native `<dialog>` element and `showModal()`, the browser marks the rest of the document as `inert`. So while a dialog from `@codenhub/toaster/dialog` is open, every toaster rendering into the same parent element places its toast stacks inside that dialog: they stay in their viewport corner, above the backdrop, and can still be clicked, focused, and heard by screen readers. They move back out as soon as the dialog starts closing. A toaster with a different container, such as one scoped to a panel, stays behind the backdrop, inert, until the dialog closes.

### Your own modal dialogs

The same applies to a `<dialog>` your application opens with `showModal()`, and the library does not move toasts into it. A toast dispatched while it is open renders behind its backdrop: it cannot be clicked, and screen readers do not announce it. To show toasts from inside your own modal, create a toaster scoped to that dialog and destroy it when the dialog closes:

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

const modal = document.querySelector<HTMLDialogElement>("#settings")!;
const modalToaster = createToaster({ container: modal });

modal.showModal();
modalToaster.success("Settings saved");

modal.addEventListener("close", () => modalToaster.destroy(), { once: true });
```

## Lifecycle states and handles

Every notification and dialog dispatched in `@codenhub/toaster` returns an active handle:

- **`ToastHandle`**: Returned by `toast()`, `toast.success()`, `toast.error()`, `toast.warning()`, `toast.info()`, `toast.loading()`, and `toast.custom()`.
- **`DialogHandle<T>`**: Returned by `dialog.confirm()`, `dialog.prompt()`, and `dialog.alert()`.

A toast notification transitions through four deterministic lifecycle states:

```
[queued] ──> [visible] ──> [hiding] ──> [hidden]
```

1. **`queued`**: The notification is admitted to the toaster queue but cannot render yet because the active stack has reached its configured `maxVisible` limit.
2. **`visible`**: The notification element is attached to the DOM and displayed in its position stack.
3. **`hiding`**: The notification has received a dismissal command and is executing its exit animation.
4. **`hidden`**: The exit animation is complete, DOM elements are detached, and the handle's `settled` promise resolves.

### The settled promise

Both handle types expose a `settled: Promise<void>` property. This promise resolves only when all exit transitions have completed and the underlying DOM elements have been completely removed from the page. Awaiting `handle.settled` gives you a deterministic point to clean up related page resources or trigger follow-up animations.

## Thenables and async operations

A major flaw in traditional toast and dialog libraries is the "truthy object" trap: an interactive method returns a handle object, and calling `if (await dialog.confirm(...))` evaluates to `true` even if the user clicks "Cancel" because an object reference is always truthy.

`@codenhub/toaster` resolves this by making `DialogHandle<T>` implement `PromiseLike<T>`:

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

// 1. Direct await resolves to the primitive boolean value:
if (await dialog.confirm("Permanently delete this project?")) {
  await executeDeletion();
}

// 2. Retaining the handle gives you both the thenable result AND programmatic controls:
const handle = dialog.confirm("Are you sure?");
console.log("Current state:", handle.state);
const confirmed = await handle;
```

Similarly, `toast.promise` binds a toast notification directly to a Promise lifecycle:

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

const data = await toast.promise(fetchUserPayload(), {
  loading: "Loading user profile...",
  success: (res) => `Welcome back, ${res.name}!`,
  error: (err) => `Failed to load profile: ${err instanceof Error ? err.message : String(err)}`,
});
```

When the underlying promise is pending, the toast renders a persistent loading indicator with auto-dismiss disabled. As soon as the promise resolves or rejects, the toast transitions to a success or error state and automatically re-arms its auto-dismiss timer. The original promise value or rejection is returned directly to the caller.

## The styling model

`@codenhub/toaster` relies on runtime CSS custom property composition rather than compiled utility classes or heavy component runtime frameworks.

When `@codenhub/styles >=0.3.0` is installed on your page, toasts and dialogs live-compose their background, foreground, border, and elevation values directly from `--color-*` tokens and UI presentation axes (`--ui-fill`, `--ui-border`, `--ui-clip`, `--ui-elevation`). If your application changes theme tokens dynamically or applies active presentation classes (`.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless`), already-rendered toasts adapt immediately.

When running standalone without `@codenhub/styles`, the required stylesheet (`@codenhub/toaster/styles`) uses pre-computed fallback colors generated from the same design token palette, ensuring consistent appearance with zero configuration.

## Next steps

- [Toast notifications](docs/toasts.md): Comprehensive guide to dispatching, updating, and queueing toasts.
- [Interactive dialogs](docs/dialogs.md): Deep dive into confirmations, prompts, alerts, and label localization.
- [Styling and theming](docs/styling.md): Presentation axes, color custom properties, dark mode, and CSP.
- [Accessibility and security](docs/accessibility.md): ARIA announcements, focus restoration, and custom content boundaries.
- [Scoped instances](docs/instances.md): Multi-instance coordination, custom containers, and lifecycle teardown.
- [API reference](docs/reference/index.md): Complete signature catalogue generated from TypeScript declarations.

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

# Toast notifications

`@codenhub/toaster` provides a callable `toast` dispatcher supporting plain neutral notifications, semantic variants, structured layouts, inline action controls, persistent loaders, and automatic Promise lifecycle binding.

## Dispatching notifications

The pre-configured `toast` singleton is callable directly for neutral notifications and exposes shorthand methods for semantic variants:

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

// Neutral notification (no severity icon or tint)
toast("Draft saved automatically");

// Semantic shorthands with built-in icons and intent styling
toast.success("Project settings published");
toast.error("Unable to connect to database");
toast.warning("Storage usage reached 85%");
toast.info("Scheduled maintenance tonight at 02:00 UTC");
```

All methods accept either a message string and an optional options object, or a single options object:

```ts
toast.success("Profile updated", { duration: 5000 });

toast.success({
  message: "Profile updated",
  duration: 5000,
});
```

## Structured content and actions

Toasts can render an optional primary title, a secondary description, and an interactive action button without requiring manual DOM manipulation:

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

toast.success("Deployment successful", {
  title: "Production v2.4.0",
  description: "All 12 edge services are healthy and receiving traffic.",
  duration: 8000,
  action: {
    label: "View logs",
    onClick: (_event, handle) => {
      openDeploymentLogs();
      handle.dismiss();
    },
  },
});
```

### Action button behavior

The `action` property renders a native `<button>` element inside the toast. When clicked, its `onClick` callback receives the standard DOM `MouseEvent` and the active `ToastHandle`. The toast does not automatically close when the action is triggered; invoke `handle.dismiss()` if you want the action to dismiss the toast.

## Loading states and Promise binding

### Manual loading toasts

`toast.loading()` dispatches a persistent toast with an animated SVG spinner. Unlike standard toasts, loading toasts do not automatically dismiss by default:

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

const loading = toast.loading("Generating analytics export...");

try {
  await generateReport();
  loading.update({
    type: "success",
    title: "Report ready",
    message: "Analytics exported to CSV.",
    autoDismiss: true,
    duration: 5000,
  });
} catch (err) {
  loading.update({
    type: "error",
    title: "Export failed",
    message: err instanceof Error ? err.message : "Unknown error",
    autoDismiss: true,
    duration: 6000,
  });
}
```

### Automated Promise binding (`toast.promise`)

`toast.promise()` automates loading state transitions, re-arming auto-dismiss, and error handling for any Promise-like operation. It returns the original Promise's resolved value so you can integrate it seamlessly into application logic:

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

const user = await toast.promise(updateUserProfile(payload), {
  loading: "Saving profile changes...",
  success: (updatedUser) => `Profile saved for ${updatedUser.username}!`,
  error: (err) => `Failed to update profile: ${err instanceof Error ? err.message : String(err)}`,
});
```

The rejection reaches `error` as `unknown`, because a promise can reject with any value, so narrow it before reading `message`.

`loading` takes a message or the options `toast.loading()` takes. The outcome is a semantic toast: settings it leaves unset come from the call's own `duration` and `dismissible`, then from the instance's `semantic` defaults, then from the instance itself.

The `success` and `error` handlers can return either a simple string or a structured `ToastOptions` object:

```ts
await toast.promise(syncRepository(), {
  loading: "Syncing changes with remote...",
  success: (result) => ({
    title: "Repository synchronized",
    description: `Fetched ${result.commits} new commits across ${result.branches} branches.`,
    action: {
      label: "View diff",
      onClick: (_event, handle) => {
        showDiffViewer();
        handle.dismiss();
      },
    },
  }),
  error: (err) => ({
    title: "Sync conflict",
    description: err instanceof Error ? err.message : String(err),
    duration: 10000,
  }),
});
```

## Programmatic control with ToastHandle

Every toast dispatch returns a `ToastHandle` providing real-time lifecycle inspection, mutation, and event subscription:

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

const handle = toast("Uploading document (0%)...", {
  autoDismiss: false,
});

// Update content dynamically as progress occurs:
onUploadProgress((percentage) => {
  handle.update({
    message: `Uploading document (${percentage}%)...`,
  });
});

// Transform into a success toast once upload finishes:
onUploadComplete(() => {
  handle.update({
    type: "success",
    message: "Document uploaded successfully!",
    autoDismiss: true,
    duration: 4000,
  });
});

// Dismiss the toast programmatically at any time:
cancelButton.addEventListener("click", () => {
  handle.dismiss();
});
```

### Lifecycle subscribers

You can listen to specific transitions in the toast's lifecycle:

```ts
handle.onShow(() => console.log("Toast element inserted into the DOM"));
handle.onShown(() => console.log("Toast entrance animation completed"));
handle.onHide(() => console.log("Toast dismissal requested, exit animation starting"));
handle.onHidden(() => console.log("Toast removed from the DOM"));

// Or await complete settlement:
await handle.settled;
console.log("Toast is completely removed from the page");
```

## Stack positioning and margins

By default, toasts are rendered at `"top-right"`. You can configure a different position per call or globally:

```ts
toast("Centered alert", { position: "top-center" });
toast("Bottom notification", { position: "bottom-right" });
```

Supported positions are:

- `"top-left"`, `"top-center"`, `"top-right"`
- `"bottom-left"`, `"bottom-center"`, `"bottom-right"`

### Margin and safe-area insets

Toasts automatically respect device safe-area insets (`env(safe-area-inset-*)`), preventing corner stacks from overlapping with mobile notches or home indicators. You can specify custom margins per call or per instance:

```ts
toast("Offset notification", {
  margin: "24px",
});

// Or independent horizontal and vertical margins:
toast("Offset notification", {
  margin: { x: "16px", y: "32px" },
});
```

## Admission control and queueing

Each toast position manages a vertical stack with a default capacity of `maxVisible: 5`. When additional toasts are dispatched beyond this capacity:

1. New toasts enter a first-in, first-out (FIFO) queue.
2. The oldest eligible toast in the visible stack begins its exit animation to make room for the new arrival.
3. If an active toast is **currently hovered**, **currently focused**, or in an **active loading state**, it is protected against eviction and will not be dismissed early.
4. When the available viewport height is smaller than the stack's total height, the stack scrolls internally without showing platform scrollbars, ensuring all toasts remain accessible.

## Clearing and dismissing toasts

To clear notifications across the instance:

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

// Dismiss all visible and queued toasts:
toast.clear();

// Dismiss an individual toast by handle:
toast.dismiss(handle);

// Dismiss all toasts (identical to clear()):
toast.dismiss();
```

## Custom content

When text formatting, custom icons, or complex controls are required, use `toast.custom()`:

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

// 1. Plain text: a string is never parsed as HTML, so markup in it shows literally.
toast.custom("Release notes are ready.");

// 2. Direct DOM node, for markup such as a link:
const container = document.createElement("p");
const link = document.createElement("a");
link.href = "/changelog";
link.textContent = "new release notes";
container.append("Visit our ", link, "!");
toast.custom(container);

// 3. Node factory receiving the active handle:
toast.custom((handle) => {
  const button = document.createElement("button");
  button.textContent = "Close me";
  button.onclick = () => handle.dismiss();
  return button;
});
```

A DOM node is trusted and inserted as it is. Read [Accessibility and security](docs/accessibility.md) for the trust boundaries of strings and nodes.

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

# Interactive modal dialogs

`@codenhub/toaster/dialog` provides native HTML5 modal dialogs through the `dialog` singleton and independent instances made with `createDialogs()`. Dialogs have their own entry point so an app that only shows toasts does not bundle them. Dialogs render directly into the browser's top layer via `HTMLDialogElement.showModal()`, guaranteeing proper accessibility, focus containment, backdrop styling, and keyboard handling.

## Why native dialogs?

Rather than simulating modal overlays with complex `z-index` stacks and manual focus traps, `@codenhub/toaster` uses the browser's native `<dialog>` platform primitive:

- **Browser Top Layer**: Always renders above every other element in the DOM tree, regardless of ancestor `z-index` or `overflow: hidden` rules.
- **Inert Document**: The browser automatically disables user interaction with the background document while the modal is open. The toasts of every toaster rendering into the same parent element move into the dialog for that time, so they stay usable (see [Concepts](docs/concepts.md#the-two-presentation-layers)). Your own modal dialogs need a scoped toaster for that (see [Your own modal dialogs](docs/concepts.md#your-own-modal-dialogs)).
- **Focus Trap & Restoration**: Focus is automatically confined within the dialog, and restored to the triggering element when the dialog closes.
- **Escape Key Handling**: Pressing Escape triggers standard dialog dismissal, resolving the operation with its cancel value.

## Confirmation dialogs (`dialog.confirm`)

`dialog.confirm()` prompts the user to confirm or cancel an operation, returning a promise that resolves to a boolean:

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

if (await dialog.confirm("Are you sure you want to discard unsaved changes?")) {
  revertForm();
}
```

### Custom button labels and destructive variants

You can customize the confirm and cancel button text, and specify `type: "error"` for destructive actions (which tints the confirm button with error intent styling):

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

const confirmed = await dialog.confirm("Permanently delete this project and all its databases?", {
  title: "Delete Project",
  type: "error",
  confirmLabel: "Delete project",
  cancelLabel: "Keep project",
});

if (confirmed) {
  await deleteProject();
}
```

## Prompt dialogs (`dialog.prompt`)

`dialog.prompt()` displays an interactive text input, resolving to the entered `string` if submitted or `null` if cancelled:

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

const branchName = await dialog.prompt("Enter a name for the new branch:", {
  title: "Create Branch",
  defaultValue: "feature/",
  placeholder: "feature/branch-name",
  submitLabel: "Create branch",
  cancelLabel: "Cancel",
});

if (branchName !== null && branchName.trim().length > 0) {
  await git.createBranch(branchName);
}
```

Pressing <kbd>Enter</kbd> inside the prompt's text input automatically submits the form, and <kbd>Escape</kbd> cancels the prompt and resolves to `null`.

## Alert dialogs (`dialog.alert`)

`dialog.alert()` provides an accessible replacement for `window.alert()`. It interrupts the user with a message and resolves to `void` when acknowledged:

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

await dialog.alert("Your session has expired. Please sign in again.", {
  title: "Session Expired",
  okLabel: "Sign in",
});

redirectToLogin();
```

## The thenable handle contract

All dialog methods return an `DialogHandle<T>`. This handle implements `PromiseLike<T>`, meaning you can either `await` the method call directly or capture the handle object for programmatic control:

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

// Approach 1: Direct await (resolves to boolean)
const confirmed = await dialog.confirm("Proceed with update?");

// Approach 2: Retain the handle
const handle = dialog.confirm("Proceed with update?");

// Inspect dialog state:
console.log(handle.state); // "visible"

// Dismiss programmatically after a timeout:
setTimeout(() => {
  if (handle.state === "visible") {
    handle.dismiss(); // Settles the promise with `false`
  }
}, 10000);

// Await the outcome when needed:
const result = await handle;
await handle.settled; // Resolves once exit animation and cleanup finish
```

### Cancellation fallbacks

When a dialog is dismissed without confirmation (by clicking the Cancel button, pressing <kbd>Escape</kbd>, or calling `handle.dismiss()`), it settles deterministically with:

- `false` for `dialog.confirm()`
- `null` for `dialog.prompt()`
- `undefined` for `dialog.alert()`

## Localization and default labels

Instead of configuring `confirmLabel`, `cancelLabel`, `submitLabel`, and `okLabel` on every call, you can set default dialog labels on an instance using `DialogsConfig.labels`:

```ts
import { createDialogs } from "@codenhub/toaster/dialog";

const dialogs = createDialogs({
  labels: {
    confirm: "Confirmar",
    cancel: "Cancelar",
    submit: "Enviar",
    ok: "Entendido",
  },
});

// All dialogs opened from this instance use Portuguese button labels:
if (await dialogs.confirm("Deseja prosseguir?")) {
  salvarAlteracoes();
}
```

A label passed directly to a dialog call (`ConfirmOptions.confirmLabel`, etc.) always overrides the instance-level default. The `dismiss` and `notifications` labels belong to toasts; set them with `ToasterConfig.labels` (see [Instances](docs/instances.md)).

## Dialog instances (`createDialogs`)

`createDialogs()` returns an independent instance with the same `confirm`, `prompt`, and `alert` methods as the `dialog` singleton. Each instance opens its dialogs one at a time, queuing later calls until the open one closes.

| Option      | Type           | Default          | Description                                                                |
| :---------- | :------------- | :--------------- | :------------------------------------------------------------------------- |
| `container` | `HTMLElement`  | `document.body`  | Element the dialogs are appended to. Cannot be changed after construction. |
| `labels`    | `DialogLabels` | English defaults | Default button labels: `confirm`, `cancel`, `submit`, `ok`.                |
| `className` | `string`       | `undefined`      | Class appended to every dialog in this instance.                           |

`dialogs.configure({...})` changes any option except `container`. `dialogs.destroy()` closes the open dialog and drops queued ones, settling each with its cancel value, and removes the instance's `<dialog>` element; later calls throw. To recolor dialogs, set the `--toast-color-*` custom properties in CSS (see [Styling](docs/styling.md#color-overrides)).

## Toasts while a dialog is open

A modal dialog makes the rest of the page inert, so a toast rendered outside it can be neither clicked nor announced. While a dialog from `@codenhub/toaster/dialog` is open, the toast stacks and live regions of every toaster rendering into the same parent element move inside it, and move back once it starts closing. Nested dialogs hand the toasts to the most recently opened one. The two entry points coordinate through an attribute on the open dialog, so using both costs nothing extra in either bundle.

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

# Styling and theming

`@codenhub/toaster` uses an adaptable CSS architecture built on runtime CSS custom property composition. It composes seamlessly with `@codenhub/styles` design tokens when present, but remains fully usable standalone with zero external dependencies.

## The required stylesheet

Import `@codenhub/toaster/styles` once in your application entrypoint:

```ts
import "@codenhub/toaster/styles";
```

This stylesheet provides layout containers, responsive positioning, FLIP animation primitives, native `<dialog>` styles, and color formula rules. No Tailwind configuration or build plugins are required.

## Live composition with `@codenhub/styles`

If your project installs `@codenhub/styles >=0.3.0`, toaster components participate automatically in its live theme and aesthetic system:

- **Semantic intents**: Toast and dialog colors read directly from `--color-<intent>` (`--color-success`, `--color-error`, `--color-warning`, `--color-info`, `--color-primary`).
- **Presentation axes**: Toasts adapt to `--ui-fill` and `--ui-border` percentages.
- **Aesthetic materials**: Active aesthetic classes (such as `.glass` or `.neobrutalism`) pass through their `--ui-radius`, `--ui-border-width`, `--ui-surface-shadow`, `--ui-clip`, and `--ui-elevation` properties.

### Component role defaults

To match the wider ecosystem, toaster components mirror the base styling conventions of `@codenhub/styles`:

- **Toast body**: Composes like an unstyled `.alert` component: a soft surface (12% intent fill), edged border, grounded on the page background with elevation 0.
- **Dialog action buttons**: Compose like an unstyled `.btn` component: a solid surface (100% intent fill), edgeless border, and raised elevation 1.

Every toast is 22.25rem (356px) wide, narrowed to fit the viewport inside the stack's margin, so a stack keeps straight edges. Set `width` through `className` to change it.

### Presentation classes

Presentation helper classes (`.solid`, `.soft`, `.ghost`, `.edged`, `.edgeless`) adjust how background fills and borders render:

```ts
import { toast } from "@codenhub/toaster";
import { dialog } from "@codenhub/toaster/dialog";

// Render a high-contrast solid toast:
toast.success("Build deployed", {
  className: "solid",
});

// Render a ghost notification:
toast.info("Syncing...", {
  className: "ghost",
});
```

Because presentation classes set CSS custom properties (`--ui-fill`, `--ui-border`), a class applied directly to a toast element overrides any presentation class declared on an ancestor element.

## Standalone fallback

When `@codenhub/styles` is not installed, `@codenhub/toaster/styles` executes the exact same color-mixing formulas against internal, pre-computed defaults (`generated-defaults.css`). These defaults are generated directly from the canonical `@codenhub/styles` palette at build time, ensuring pixel-consistent aesthetics without introducing runtime dependencies.

## Dark mode support

Dark mode styling activates automatically when an ancestor element (typically `<html>`) carries either:

- The `.dark` class
- The `data-theme="dark"` attribute

Because colors resolve through CSS `light-dark()` against the element's computed `color-scheme`, dark mode works consistently in both standalone and composed modes.

### Nested themes

Theme resolution is computed locally. If your application embeds a `.light` or `.dark` section inside an oppositely-themed parent, toasts or dialogs rendered inside that container inherit the correct local colors:

```html
<html class="dark">
  <body>
    <!-- Nested light-themed workspace panel -->
    <div id="light-panel" class="light" style="color-scheme: light">
      <!-- Toaster anchored inside this panel uses light-mode tokens -->
    </div>
  </body>
</html>
```

## Color overrides

When you need colors that deviate from the active theme, set the package's `--toast-color-*` CSS custom properties in your own stylesheet. The stylesheet reads each one ahead of the composed formula, so a property you set always supersedes the composed color for that specific value. Custom properties inherit, so where you set one decides what it reaches:

```css
/* Globally: every toast and dialog on the page. */
:root {
  --toast-color-success-bg: #0f2e1b;
  --toast-color-success-fg: #4ade80;
  --toast-color-success-edge: #166534;
}

/* Scoped: only toasts rendered inside this container. */
#billing-panel {
  --toast-color-error-bg: #450a0a;
  --toast-color-error-fg: #fca5a5;
  --toast-color-error-edge: #991b1b;
}

/* Per toast: a class passed through `className`. */
.storage-alert {
  --toast-color-error-bg: #7f1d1d;
}
```

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

// Scoped: a toaster whose stacks render inside #billing-panel.
const billingToaster = createToaster({ container: document.querySelector<HTMLElement>("#billing-panel")! });

// Per toast:
toast.error("Critical storage alert", { className: "storage-alert" });
```

`ToasterConfig.className` applies a class to every toast of an instance, and `DialogsConfig.className` or a dialog's own `className` applies one to the `<dialog>` element. The package sets these properties nowhere itself, so it injects no `<style>` element for them.

### Available properties

| Property                     | What it colors                                                                            |
| ---------------------------- | ----------------------------------------------------------------------------------------- |
| `--toast-color-default-bg`   | Background of a default (no-severity) toast.                                              |
| `--toast-color-default-fg`   | Text and icon color of a default toast.                                                   |
| `--toast-color-default-edge` | Border of a default toast.                                                                |
| `--toast-color-success-bg`   | Background of a success toast.                                                            |
| `--toast-color-success-fg`   | Text and icon color of a success toast.                                                   |
| `--toast-color-success-edge` | Border of a success toast.                                                                |
| `--toast-color-error-bg`     | Background of an error toast.                                                             |
| `--toast-color-error-fg`     | Text and icon color of an error toast.                                                    |
| `--toast-color-error-edge`   | Border of an error toast.                                                                 |
| `--toast-color-warning-bg`   | Background of a warning toast.                                                            |
| `--toast-color-warning-fg`   | Text and icon color of a warning toast.                                                   |
| `--toast-color-warning-edge` | Border of a warning toast.                                                                |
| `--toast-color-info-bg`      | Background of an info toast.                                                              |
| `--toast-color-info-fg`      | Text and icon color of an info toast.                                                     |
| `--toast-color-info-edge`    | Border of an info toast.                                                                  |
| `--toast-color-surface`      | Background of the dialog container and its cancel button.                                 |
| `--toast-color-text`         | Text color of the dialog container and its cancel button.                                 |
| `--toast-color-border`       | Border of the dialog container and cancel button under `.edged`, and of the prompt input. |
| `--toast-color-primary-bg`   | Focus ring and focused border of the prompt input.                                        |

Every value is final, a background, border, or foreground exactly as it renders, never an ingredient the package mixes further. Unset, each one matches the composed `@codenhub/styles` look, or the package's generated defaults without it.

## Content Security Policy (CSP)

`@codenhub/toaster` injects no `<style>` element: the stylesheet is one you load, and the package writes only individual inline properties through the CSSOM (`element.style.setProperty`), which a `style-src` policy does not block. A strict policy such as the following needs no nonce for the package:

```http
Content-Security-Policy: style-src 'self';
```

## Classless native button stylesheet ordering

If your application uses `@codenhub/styles`' classless native-element mappings (`@codenhub/styles/native` or `@codenhub/styles/tw/native`), be mindful of stylesheet import order:

```ts
// Correct import order:
import "@codenhub/styles/native";
import "@codenhub/toaster/styles";
```

`@codenhub/styles/native` resets every bare `<button>` element to neutral intent at zero specificity. Because `@codenhub/toaster`'s dialog buttons are bare `<button>` elements that also use zero specificity, importing native mappings _after_ `@codenhub/toaster/styles` will overwrite dialog button colors. Always import the native mappings first.

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

# Accessibility and security

`@codenhub/toaster` is designed to be accessible to assistive technologies by default and renders custom string content as plain text, never as HTML.

## Toast announcements and ARIA live regions

Screen readers hear toasts through two visually hidden live regions that each toaster instance adds to its container: one polite, one assertive. A toast's `role` decides which one speaks its text:

- **`"status"`**, the default for `toast()`, `toast.success()`, `toast.info()`, and `toast.loading()`: announced through the polite region, after any speech in progress.
- **`"alert"`**, the default for `toast.error()` and `toast.warning()`: announced through the assertive region, interrupting speech in progress.

You can override the role per call when needed:

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

toast.warning("Storage is almost full", {
  role: "status",
});
```

The announcement is the toast's title, description, or message, or the text of its custom content. When a toast has an `action`, its label follows, as in "Deleted. Undo", so a screen reader user knows there is something to act on. The dismiss button and buttons inside custom content are left out. A toast is announced again when an update changes its text or role, so a promise toast announces both its loading text and its outcome. The visible toast element is not itself a live region.

A live region inserted together with its text is often missed, because assistive technology only announces changes to a region it already tracks. Each region is therefore added empty, and its first text follows 100ms later, as in the [W3C ARIA22 technique](https://www.w3.org/WAI/WCAG21/Techniques/aria/ARIA22.html). Announced text is removed from the region after 7 seconds.

While a dialog from `@codenhub/toaster/dialog` is open, every toaster rendering into the same parent element announces through live regions inside that dialog, because the rest of the page is inert then. Toasts from a toaster with a different container are not announced while the dialog is open, and the library does not repeat them after it closes. The same holds under a modal dialog your application opens itself; see [Your own modal dialogs](docs/concepts.md#your-own-modal-dialogs).

Each toast stack is a `region` landmark named "Notifications", so screen reader users can jump to it to reach a toast's buttons. Set `labels.notifications` to localize the name.

### Accessible dismiss buttons

When a toast is dismissible (`dismissible: true`), its close button includes an accessible `aria-label="Dismiss toast"`. You can localize this label across an entire instance:

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

const toaster = createToaster({
  labels: {
    dismiss: "Fechar notificação",
  },
});
```

## Native dialog accessibility

Interactive modal dialogs leverage browser-level accessibility:

- **Top layer semantics**: Rendered with `<dialog>` and `showModal()`, automatically hiding background content from the accessibility tree via browser `inert`.
- **Focus trapping**: Tab navigation cycles strictly within the active dialog, preventing focus from escaping to hidden background elements.
- **Focus restoration**: When the dialog closes, focus returns automatically to the element that was focused immediately before the dialog was opened.
- **Dismissal controls**: Pressing <kbd>Escape</kbd> triggers cancellation automatically.

## Reduced motion (`prefers-reduced-motion`)

Users with vestibular disorders or motion sensitivities often enable reduced motion in their operating systems.

`@codenhub/toaster` listens to `matchMedia("(prefers-reduced-motion: reduce)")`. When active:

- Entrance and exit animations resolve with 0ms duration.
- FLIP layout transitions update element positions immediately without transitional sliding.
- Dialog backdrops appear and disappear without fade transitions.

## Custom content security boundaries

`toast.custom()` takes either a string or a DOM node, and each has its own trust boundary:

```
┌──────────────────────────────────────────────────────────┐
│                      toast.custom()                      │
├─────────────────────────────┬────────────────────────────┤
│         Text String         │      Trusted DOM Node      │
│  - Rendered as plain text   │  - Unmodified insertion    │
│  - Never parsed as HTML     │  - Full developer control  │
│  - Safe for untrusted text  │  - Developer owns security │
└─────────────────────────────┴────────────────────────────┘
```

### Text strings

A string passed to `toast.custom()`, or as `content` to a toast or an `update()`, is inserted as a text node. It is never parsed as HTML, so markup in it shows literally and creates no elements:

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

// Shows the characters `<b>Saved</b>`, not bold text.
toast.custom("<b>Saved</b>");
```

This makes strings safe for untrusted text such as user names or server messages. A string that is empty or only whitespace throws an error rather than rendering a blank notification.

### Trusted Types and Content Security Policy

The package never assigns to `innerHTML` or any other HTML-parsing sink: strings become text nodes, and built-in toasts, icons, and dialogs are built with DOM APIs. A Content Security Policy with `require-trusted-types-for 'script'` needs no Trusted Types policy for it.

### Trusted DOM nodes

If you need formatting, links, buttons with event listeners, custom SVG icons, or complex layouts, pass a DOM `Node` or a node factory function:

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

toast.custom((handle) => {
  const container = document.createElement("div");
  container.className = "custom-toast-layout";

  const text = document.createElement("span");
  text.textContent = "Review pending requests";

  const button = document.createElement("button");
  button.textContent = "Review";
  button.onclick = () => {
    openReviewModal();
    handle.dismiss();
  };

  container.append(text, button);
  return container;
});
```

Application-supplied DOM nodes are inserted as they are. The caller is responsible for their content's safety, for example by building it with DOM APIs and `textContent` rather than parsing untrusted HTML, and for their accessible names, event listeners, and memory cleanup.

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

# Scoped instances and lifecycles

While the pre-configured `toast` and `dialog` singletons serve the majority of application feedback needs, `createToaster()` allows you to instantiate independent, scoped instances for micro-frontends, embedded widgets, or isolated testing environments. Dialogs have their own factory, `createDialogs()` from `@codenhub/toaster/dialog`; see [Dialog instances](docs/dialogs.md#dialog-instances-createdialogs).

## When to create an instance

Consider creating an independent instance when:

- You need notifications contained inside an embedded overlay, side drawer, or iframe rather than the document body.
- You are developing an isolated widget or micro-frontend that must not interfere with host notifications.
- Different sections of your application require different default positions, capacities, or themes.
- You are writing unit or browser tests that require a fresh, deterministic notification stack.

## Creating and configuring an instance

`createToaster(config?)` accepts an optional `ToasterConfig` object:

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

const panelToaster = createToaster({
  container: document.getElementById("slide-over-panel")!,
  position: "bottom-left",
  maxVisible: 3,
  duration: 6000,
  dismissible: true,
  className: "panel-notifications",
  labels: {
    dismiss: "Close alert",
  },
});

// Dispatch notifications directly on the instance:
panelToaster.success("Panel settings saved");
```

### Full configuration options

| Option        | Type                   | Default          | Description                                                                                                                   |
| ------------- | ---------------------- | ---------------- | ----------------------------------------------------------------------------------------------------------------------------- |
| `container`   | `HTMLElement`          | `document.body`  | Target element holding the toast stack containers. Cannot be changed after construction.                                      |
| `position`    | `ToastPosition`        | `"top-right"`    | Viewport anchor position (`"top-left"`, `"top-center"`, `"top-right"`, `"bottom-left"`, `"bottom-center"`, `"bottom-right"`). |
| `maxVisible`  | `number`               | `5`              | Maximum number of simultaneously visible toasts before FIFO eviction begins.                                                  |
| `duration`    | `number`               | `4000`           | Default display duration in milliseconds before auto-dismissal.                                                               |
| `autoDismiss` | `boolean`              | `true`           | Whether toasts close automatically after `duration` expires.                                                                  |
| `dismissible` | `boolean`              | `false`          | Whether to render a close button on toasts.                                                                                   |
| `margin`      | `string \| { x?, y? }` | safe-area inset  | Spacing between the stack container and the viewport/container edge.                                                          |
| `className`   | `string`               | `undefined`      | Custom CSS class appended to every toast in this instance.                                                                    |
| `labels`      | `ToastLabels`          | English defaults | Default text labels: `dismiss` for close buttons and `notifications` for stacks.                                              |

## Dynamic reconfiguration (`toaster.configure`)

You can modify an instance's settings at runtime using `toaster.configure()`:

```ts
// Update capacity and durations dynamically:
toaster.configure({
  maxVisible: 8,
  duration: 3000,
  margin: "32px",
});
```

Attempting to pass `container` to `configure()` throws an error: container elements are established during instantiation and cannot be migrated dynamically.

## Teardown and cleanup with `destroy()`

To avoid memory leaks and orphaned DOM elements when an embedded component or test unmounts, invoke `toaster.destroy()`:

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

function mountWidget(containerEl: HTMLElement) {
  const toaster = createToaster({ container: containerEl });

  return function unmountWidget() {
    // Release all DOM elements, timers, listeners, and styles:
    toaster.destroy();
  };
}
```

### What happens during `destroy()`

Calling `destroy()` is synchronous, deterministic, and idempotent:

1. **Immediate handle settlement**: All visible and queued toast handles settle immediately, transitioning to state `"hidden"` and resolving their `settled` promises without waiting for exit animations.
2. **DOM cleanup**: All stack containers owned by the instance are detached from the DOM.
3. **Listener teardown**: All internal event listeners, timers, and window observers are cleared.
4. **Guard activation**: Any subsequent call to the destroyed instance throws an error synchronously.

## Multi-instance isolation

Each `Toaster` instance is fully self-contained. Two instances configured with the same position will render two independent, overlapping container trees:

```ts
const toasterA = createToaster({ position: "top-right" });
const toasterB = createToaster({ position: "top-right" });

toasterA.success("From A");
toasterB.success("From B");
```

Because both instances maintain separate admission queues, they will render on top of each other. In a single application, prefer using a single shared instance (such as the default `toast` export) with per-call overrides, and reserve separate instances for truly separated containers.

## Server-Side Rendering (SSR)

`@codenhub/toaster` is designed to be SSR-safe:

- Calling `createToaster()` or importing `toast` on the server does not throw.
- Any operation requiring the browser DOM—such as dispatching toasts or opening dialogs—checks for `document` and throws an error if called in a server environment.

In frameworks like Next.js, Remix, SvelteKit, or Astro, ensure toast dispatches happen within client event handlers, lifecycle hooks (`useEffect`, `onMount`), or browser-only entrypoints:

```tsx
// React example
"use client";

import { toast } from "@codenhub/toaster";

export function SaveButton() {
  const handleClick = () => {
    toast.success("Saved successfully");
  };

  return <button onClick={handleClick}>Save</button>;
}
```

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

# 0.4.0

## Added

- `@codenhub/toaster/dialog` exports `dialog` and `createDialogs`. While one of its dialogs is open, the toasts of every toaster rendering into the same parent move into it, as before.

## Changed

- Dialogs are no longer part of `@codenhub/toaster`, so an app that only shows toasts no longer bundles them: `toast("…")` alone costs about 9.1 KB gzipped, down from 12.7 KB. `dialog` and `toaster.dialog` are gone from the main entry; import `dialog` or `createDialogs` from `@codenhub/toaster/dialog`. The `confirm`, `cancel`, `submit`, and `ok` labels move to `DialogsConfig.labels`, a toaster's `className` and `destroy()` no longer affect dialogs, and `DialogDispatcher` is renamed `Dialogs`.
- String content passed to `toast.custom()`, the `content` option, or `update({ content })` renders as plain text, so markup in it shows literally. Pass a DOM node or a node factory for formatted content. The package no longer uses any HTML-parsing sink, so a Trusted Types policy needs nothing for it.
- Colors are set with `--toast-color-*` CSS custom properties in your own CSS. The `tokens` and `nonce` options and the `ToastTokens` type are removed, and the package injects no `<style>` element, so a strict `style-src` policy needs no nonce.

## Fixed

- Dismissing a queued dialog no longer moves the toasts out of the dialog that is still open.
- Submitting a prompt with Enter no longer activates the element that opened it when focus returns there.
- A dialog whose element was removed, or whose `body` was replaced, is recreated instead of every later dialog failing.
- An update that only changes a toast's text keeps its action and dismiss buttons, so focus on them is kept.
- A dismissed toast restores focus only when its removal lost it, not when focus moved elsewhere during the exit animation.
- An update from an `onShow` subscriber no longer renders the content twice.
- `destroy()` after `clear()` finishes leaving toasts immediately, and a toast dismissed during its entrance animation can still have its exit cancelled.

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

# 0.3.1

## Changed

- Nothing a consumer imports has changed: the bundle is byte-identical to 0.3.0's, so upgrading needs no action. The release records its size for each export imported alone, and its bytes by source file, function, and class member, for the package index to show where they go.

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

# 0.3.0

## Added

- `labels.notifications` names each toast stack, which is now a region landmark that screen reader users can jump to.
- Dialog handles are exported as `DialogHandle`.

## Changed

- Screen readers hear toasts through hidden polite and assertive live regions that exist before any toast text, instead of through the toast element. Toast elements no longer carry `role` or `aria-live`; code or tests that query toasts by those attributes need another selector, such as `.coden-toast`.
- An actionable toast's announcement includes its action label.
- While a toaster's dialog is open, that toaster's toast stacks move inside the dialog, so they stay above the backdrop, clickable, and announced. Toasts from other toaster instances are not announced while it is open.
- `InteractiveToastHandle` is renamed `DialogHandle`.
- `PromiseToastOptions.loading` is typed `string | LoadingToastOptions`. Toast-only fields such as `duration` no longer type-check there; set them on the call's other options.
- An explicit `undefined` in toast options, `configure`, or dialog options is ignored instead of erasing the default. `configure({ tokens: undefined })` clears the instance's tokens.
- `toast.promise` applies the call's options and the `semantic` defaults to its success and error toasts.
- Every toast has the same 22.25rem width. The dialog panel is wider, its backdrop blur lighter, and its open and close animation runs on the panel instead of the `dialog` element, so styles that target the element's animation need to target the panel.
- `configure({ nonce })` rewrites the token stylesheet under the new nonce, keeping the old one until the new one is ready.
- `sideEffects` covers only CSS, and the default `toast` and `dialog` singletons are pure, so importing the package without using it adds nothing to a bundle.

## Removed

- The `closeButton` option. Use `dismissible`.
- The `shouldBackdropDismiss` option. Use `backdropDismiss`.
- The `"center"` toast position.

## Fixed

- A `toast.promise` success or error formatter that throws no longer leaves the loading toast on screen. The error is reported, the loader is dismissed, and the caller still gets the original result or rejection.
- The fallback presentation styles sit in `@layer components`, so `@codenhub/styles` controls on the same page take precedence again.
- A toast dispatched just before or while a dialog opens or closes is announced, instead of being lost in a live region the dialog made inert or removed.
- Custom-content announcements keep their direct text and leave out nested button labels.
- `update` on a custom-content toast no longer adds an icon.

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

# 0.2.0

Pre-1.0 public API overhaul flattening the dispatcher surface, integrating first-class Promise lifecycles, and introducing awaitable thenable dialogs.

## Added

- Pre-configured browser singletons: top-level `toast` and `dialog` exports are available for zero-boilerplate use across applications.
- Callable root dispatcher: `toast("message", options?)` dispatches neutral notifications without requiring an arbitrary severity level or custom DOM nodes.
- Semantic shorthand dispatchers: `toast.success()`, `toast.error()`, `toast.warning()`, and `toast.info()`.
- First-class Promise lifecycle binding: `toast.promise(promise, options)` tracks pending operations, handles success and error transitions with custom message formatters, automatically re-arms auto-dismiss timers, and returns the underlying Promise value.
- Structured content and inline actions: toasts support `title`, `description`, and `action: { label, onClick: (event, handle) => void }` directly in options.
- Automated API reference documentation: compiled directly from TypeScript declarations and source TSDoc under `docs/reference/`.

## Changed

- Flattened public API: removed nested dispatcher namespaces (`toaster.semantic`, `toaster.loading`, `toaster.custom`, `toaster.interactive`) in favor of direct methods on `toast` and `dialog`.
- Thenable interactive handles: `InteractiveToastHandle<T>` returned by `dialog.confirm()`, `dialog.prompt()`, and `dialog.alert()` implements `PromiseLike<T>`. Calling `await dialog.confirm(...)` directly resolves to the primitive `boolean` (`string | null` for prompt, `void` for alert) instead of an object reference, while retaining access to `.dismiss()`, `.settled`, and `.state`.
- Standardized error vocabulary: unified all error terminology to `error` (e.g. `toast.error()`, `type: "error"`, `errorBg`/`errorEdge`/`errorBtnBg` tokens), replacing legacy `"danger"` and `"destructiveBg"`.
- Standardized boolean options: renamed `isDismissable` to `dismissible` (with `closeButton` supported as an alias) and `shouldAutoDismiss` to `autoDismiss`.

## Removed

- Legacy nested namespaces: `toaster.semantic`, `toaster.loading`, `toaster.custom`, and `toaster.interactive` are removed.

## Fixed

- Re-armed auto-dismissal on promise loaders: updating a loading toast to a success or error state automatically re-arms the auto-dismiss timer instead of leaving the notification on screen indefinitely.
- Layout rebuilds on toast updates: updating message and title properties preserves layout structure and secondary descriptions when transitioning between simple and structured formats.

<!-- 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.
