# Overview
One toast runtime, three entry points. Pick the package that matches your app and keep the same mental model everywhere.
:docs-npm-badges{packages="vue-toastflow:43b883,nuxt-toastflow:02a361,toastflow-core:64748b"}
::card-group
:::card
---
icon: i-simple-icons-vuedotjs
title: Vue
to: https://www.toastflow.top/docs/vue/quick-start
---
`vue-toastflow`
— Plugin + components for any Vue 3 app.
:::
:::card
---
icon: i-simple-icons-nuxtdotjs
title: Nuxt
to: https://www.toastflow.top/docs/nuxt/quick-start
---
`nuxt-toastflow`
— Zero-config module with auto-imports and SSR.
:::
:::card
---
icon: i-tabler-automation
title: Core
to: https://www.toastflow.top/docs/headless/core-store
---
`toastflow-core`
— Framework-agnostic store. Bring your own renderer.
:::
::
## Package Map
::code-tree{expand-all default-value="vue/main.ts"}
```ts [vue/main.ts]
import { createApp } from "vue";
import { createToastflow } from "vue-toastflow";
import App from "./App.vue";
createApp(App).use(createToastflow()).mount("#app");
```
```vue [vue/App.vue]
```
```ts [nuxt/nuxt.config.ts]
export default defineNuxtConfig({
modules: ["nuxt-toastflow"],
toastflow: {
config: { position: "top-right", duration: 5000 },
},
});
```
```vue [nuxt/app.vue]
```
```ts [headless/store.ts]
import { createToastStore } from "toastflow-core";
export const toastStore = createToastStore({
position: "top-right",
duration: 5000,
});
toastStore.subscribe((state) => {
// Render `state.toasts` with your own UI.
});
toastStore.show({ type: "success", title: "Saved" });
```
::
## Shared Runtime
::steps
### Create
Use a payload object, a title + options call, or a typed helper like `toast.success(...)`.
### Update
Keep the returned id when you want to turn a loading toast into a success or error toast.
### Control
Pause the queue, dismiss all toasts, subscribe to state, or listen to lifecycle events.
::
::code-group
```ts [basic.ts]
toast.success({
title: "Saved",
description: "Your changes are live.",
});
```
```ts [loading.ts]
const request = toast.loading(fetch("/api/save"), {
loading: { title: "Saving" },
success: { title: "Saved" },
error: { title: "Failed" },
});
await request;
```
```ts [store.ts]
toast.dismissAll();
toast.pauseQueue();
toast.resumeQueue();
toast.subscribe((state) => {
console.log(state.toasts.length);
});
```
::
## Live Runtime
::docs-live-example
## What Is Covered
:::card-group
::::card
---
icon: i-tabler-package
title: Install paths
to: https://www.toastflow.top/docs/vue/quick-start
---
Vue plugin setup, Nuxt module setup, and the headless core entry point.
::::
::::card
---
icon: i-tabler-stopwatch
title: Runtime behavior
to: https://www.toastflow.top/docs/vue/timers-and-progress
---
Timers, progress bars, queue behavior, duplicate prevention, and loading flows.
::::
::::card
---
icon: i-tabler-file-code-2
title: API surface
to: https://www.toastflow.top/docs/api/runtime-exports
---
Shared exports, typed actions, state, events, config, and core utilities.
::::
::::card
---
icon: i-tabler-palette
title: Customization
to: https://www.toastflow.top/docs/global/styling
---
CSS variables, theme classes, inline overrides, shipped CSS control, and headless rendering.
::::
:::
## Practical Rules
:::accordion
::::accordion-item{label="Where should I start?"}
Use Vue quick start for Vue apps, Nuxt quick start for Nuxt apps, and Headless only when you need to render everything yourself.
::::
::::accordion-item{label="Do Vue and Nuxt use different toast APIs?"}
No. Nuxt adds module wiring and auto-imports, but
`toast.show`
,
`toast.success`
,
`toast.loading`
,
`toast.update`
, and store controls behave the same.
::::
::::accordion-item{label="When should I use headless mode?"}
Use it when Toastflow's default card is not enough. In Vue, try the headless slot first. Outside Vue, use
`toastflow-core`
.
::::
::::accordion-item{label="Where is the API reference?"}
See
[Configuration](https://www.toastflow.top/docs/api/configuration)
,
[Actions](https://www.toastflow.top/docs/api/actions)
,
[Events](https://www.toastflow.top/docs/api/events)
, and
[State](https://www.toastflow.top/docs/api/state)
.
::::
:::
::
# Styling
Toastflow is CSS-first. Start with variables, use theme classes for reusable variants, and keep inline `css` for one-off toasts.
::tip
Most apps only need CSS variables. You do not need a design-system wrapper just to make Toastflow match your brand.
::
## The Three Levels
::tabs
:::tabs-item{label="Global variables"}
Use this for the default app-wide look.
```ts [toast.ts]
toast.info({
title: "Global variables",
description: "Font, radius, and color tokens from :root.",
});
```
```css [app.css]
:root {
--tf-toast-font-family:
"IBM Plex Mono", "SFMono-Regular", Consolas, monospace;
--tf-toast-border-radius: 4px;
--tf-toast-padding: 18px;
--tf-toast-bg: #fff7ed;
--tf-toast-color: #431407;
--tf-toast-title-color: #431407;
--tf-toast-description-color: #7c2d12;
--tf-toast-border-color: #fb923c;
--tf-toast-icon-color: #ea580c;
--tf-toast-progress-bg: color-mix(in srgb, #ea580c 18%, transparent);
--tf-toast-progress-bar-bg: #ea580c;
}
```
:::
:::tabs-item{label="Theme class"}
Use this when a toast should opt into a named visual style.
`theme: "brand"` maps to `.tf-toast-accent--brand`. If you pass a full class name that already starts with `tf-toast-accent--`, Toastflow uses it as-is.
```ts [toast.ts]
toast.info({
title: "Release",
description: "v1.0.0 is out.",
theme: "brand",
});
```
```css [app.css]
.tf-toast-accent--brand {
--tf-toast-bg: #ecfeff;
--tf-toast-color: #164e63;
--tf-toast-border-color: #67e8f9;
--tf-toast-title-color: #0e7490;
--tf-toast-description-color: #155e75;
--tf-toast-progress-bg: color-mix(in srgb, #0891b2 20%, transparent);
--tf-toast-progress-bar-bg: #0891b2;
--tf-toast-icon-color: #0891b2;
}
```
:::
:::tabs-item{label="Inline override"}
Use this only for one-off cases generated from runtime data.
```ts [toast.ts]
toast.custom({
title: "Runtime accent",
description: "One-off color override, inherited global structure.",
css: {
bg: "#450a0a",
color: "#fff1f2",
titleColor: "#ffe4e6",
descriptionColor: "#fecdd3",
borderColor: "#fb7185",
iconColor: "#fbbf24",
progressBg: "color-mix(in srgb, #fbbf24 20%, transparent)",
progressBarBg: "#fbbf24",
},
});
```
:::
::
The live preview below mirrors all three tabs. It scopes the global variables around a local preview container, so **Theme class** and **Inline override** inherit the global font, radius, and spacing while overriding only their own color tokens.
::docs-toast-preview{variant="styling"}
## Common Variables
| Variable | Controls |
| ------------------------------ | ------------------- |
| `--tf-toast-bg` | Toast background |
| `--tf-toast-color` | Title and main text |
| `--tf-toast-description-color` | Description text |
| `--tf-toast-border-color` | Card border |
| `--tf-toast-border-radius` | Card radius |
| `--tf-toast-font-family` | Card typography |
| `--tf-toast-padding` | Card padding |
| `--tf-toast-progress-bar-bg` | Progress bar fill |
| `--tf-toast-progress-bg` | Progress track |
| `--tf-toast-icon-color` | Current toast icon |
| `--tf-toast-icon-success` | Success icon color |
| `--tf-toast-icon-error` | Error icon color |
## Disable Shipped CSS
Disable the packaged stylesheet when you want to own every Toastflow CSS variable yourself. The setup is different for Nuxt and plain Vue.
:::code-tree{expand-all default-value="nuxt/nuxt.config.ts"}
```ts [nuxt/nuxt.config.ts]
export default defineNuxtConfig({
modules: ["nuxt-toastflow"],
css: ["~/assets/toastflow.css"],
toastflow: {
css: false,
},
});
```
```css [nuxt/assets/toastflow.css]
:root {
--tf-toast-font-family:
"IBM Plex Mono", "SFMono-Regular", Consolas, monospace;
--tf-toast-border-radius: 4px;
--tf-toast-padding: 18px;
--tf-toast-bg: #fff7ed;
--tf-toast-color: #431407;
--tf-toast-border-color: #fb923c;
--tf-toast-icon-color: #ea580c;
}
```
```ts [vue/src/main.ts]
import { createApp } from "vue";
import { createToastflow } from "vue-toastflow";
import "./toastflow.css";
createApp(App)
.use(createToastflow({}, { css: false }))
.mount("#app");
```
```css [vue/src/toastflow.css]
:root {
--tf-toast-font-family:
"IBM Plex Mono", "SFMono-Regular", Consolas, monospace;
--tf-toast-border-radius: 4px;
--tf-toast-padding: 18px;
--tf-toast-bg: #fff7ed;
--tf-toast-color: #431407;
--tf-toast-border-color: #fb923c;
--tf-toast-icon-color: #ea580c;
}
```
:::
## Color Override Priority
When multiple layers set the same color, Toastflow resolves in this order:
1. **Per-toast `css` prop** (inline style)
2. **Per-toast `theme` class** (`.tf-toast-accent--brand`)
3. **Global override** (`--tf-toast-bg` on `:root`)
4. **Built-in type/default preset** (`success`, `error`, `custom`, ...)
`--tf-toast-color` cascades to title and description colors unless `--tf-toast-title-color` or `--tf-toast-description-color` is explicitly set.
## Tailwind CSS v4
Use `@layer base` to override Toastflow tokens in Tailwind v4:
```css [app.css]
@layer base {
:root {
--tf-toast-bg: var(--color-background-subtle);
--tf-toast-color: var(--color-foreground);
--tf-toast-border-color: var(--color-border);
--tf-toast-border-radius: var(--radius-lg);
--tf-toast-font-family: var(--font-sans);
}
}
```
:::warning
`@theme`
will not work — Tailwind wraps
`@theme`
in
`@layer theme`
, which has lower priority than Toastflow's unlayered styles.
:::
## More Variable Groups
| Group | Examples |
| ------------------ | ----------------------------------------------------------------------- |
| **Layout** | `--tf-toast-padding`, `--tf-toast-gap`, `--tf-toast-border-radius` |
| **Stack** | `--tf-toast-stack-padding-top`, `--tf-toast-stack-padding-right` |
| **Typography** | `--tf-toast-title-font-size`, `--tf-toast-description-line-height` |
| **Close button** | `--tf-toast-close-size`, `--tf-toast-close-icon-size` |
| **Action buttons** | `--tf-toast-button-*` |
| **Progress** | `--tf-toast-progress-height`, `--tf-toast-progress-duration` |
| **Motion** | `--tf-toast-animation-in-duration`, `--tf-toast-animation-out-duration` |
| **Shorthands** | `--tf-toast-accent-color`, `--tf-toast-icon-color` |
:::note
**Full variable reference:**
every variable and its default lives in
[`packages/vue/src/styles.css`](https://github.com/adrianjanocko/toastflow/blob/main/packages/vue/src/styles.css){rel=""nofollow"" target="\_blank"}
— the
`:root`
block is the source of truth.
:::
## The `accentColor` Shorthand
In the per-toast `css` prop, `accentColor` sets the text, title, description, and progress bar colors at once:
```ts [accent-color.ts]
toast.custom({
title: "One color, four tokens",
css: {
accentColor: "#0891b2",
},
});
```
## HTML Support
`supportHtml: true` enables HTML in `title` and `description`:
```ts [html-support.ts]
toast.info({
title: "Release",
description: 'Read changelog',
supportHtml: true,
});
```
:::caution
Toastflow does not sanitize HTML by default — this applies to
`supportHtml`
content
**and**
`html`
buttons. Rendering untrusted input (user submissions, API error messages, URL params) this way is an XSS vector. Configure the
`sanitizer`
hook below, sanitize the content yourself before passing it in, or keep
`supportHtml`
off.
:::
### Sanitizer Hook
Set `sanitizer` once in the global config and every HTML render (`supportHtml` titles/descriptions and `html` buttons) goes through it:
```ts [sanitizer.ts]
import DOMPurify from "dompurify";
app.use(
createToastflow({
supportHtml: true,
sanitizer: (html) => DOMPurify.sanitize(html),
}),
);
```
The hook receives the raw HTML string and returns the sanitized string. If it throws, the content is dropped instead of rendered raw. Like any other option, it can be overridden per toast.
:::note
In Nuxt,
`sanitizer`
is a function and cannot live in
`nuxt.config`
(config is JSON-serialized). Configure it in a client plugin instead — see
[Nuxt Notes](https://www.toastflow.top/docs/nuxt/nuxt-notes)
.
:::
## When CSS Is Not Enough
:::card-group
::::card
---
icon: i-tabler-template
title: Headless slot
to: https://www.toastflow.top/docs/headless/headless-slot
---
Keep the Vue store and accessibility helpers, but render your own toast card.
::::
::::card
---
icon: i-tabler-automation
title: Core store
to: https://www.toastflow.top/docs/headless/core-store
---
Use the framework-agnostic store and build your renderer from scratch.
::::
:::
::
# Vue Quick Start
::docs-npm-badges{downloads packages="vue-toastflow:43b883"}
::
Use `vue-toastflow` when you have a normal Vue app entry file.
::steps
### Install
:::code-group
```bash [pnpm]
pnpm add vue-toastflow
```
```bash [npm]
npm install vue-toastflow
```
```bash [yarn]
yarn add vue-toastflow
```
:::
### Register the plugin
```ts [main.ts]
import { createApp } from "vue";
import App from "./App.vue";
import { createToastflow } from "vue-toastflow";
createApp(App).use(createToastflow()).mount("#app");
```
### Render one container
```vue [App.vue]
```
### Fire a toast
```ts [save.ts]
import { toast } from "vue-toastflow";
toast.success({
title: "Saved",
description: "Your changes are live.",
});
```
::
::warning
`toast.*`
needs the plugin-created store. Install
`createToastflow()`
before calling toasts from stores, services, or composables.
::
## Minimal File Tree
::code-tree{expand-all default-value="src/main.ts"}
```ts [src/main.ts]
import { createApp } from "vue";
import App from "./App.vue";
import { createToastflow } from "vue-toastflow";
createApp(App).use(createToastflow()).mount("#app");
```
```vue [src/App.vue]
```
```vue [src/components/ProfileForm.vue]
```
```ts [src/features/save.ts]
import { toast } from "vue-toastflow";
export async function saveProfile() {
await toast.loading(fetch("/api/profile"), {
loading: { title: "Saving profile" },
success: { title: "Profile saved" },
error: { title: "Save failed" },
});
}
```
::
## Next Steps
::card-group
:::card
---
icon: i-tabler-bell
title: Toasts
to: https://www.toastflow.top/docs/vue/toasts
---
Types, create call styles, per-toast overrides, positions, duplicates, and lifecycle hooks.
:::
:::card
---
icon: i-tabler-stopwatch
title: Timers & Progress
to: https://www.toastflow.top/docs/vue/timers-and-progress
---
Duration rules, pause strategies, queue behavior, and the loading helper.
:::
:::card
---
icon: i-tabler-hand-click
title: Buttons & Actions
to: https://www.toastflow.top/docs/vue/buttons-and-actions
---
Action buttons, close behavior, and timestamps.
:::
:::card
---
icon: i-tabler-palette
title: Styling
to: https://www.toastflow.top/docs/global/styling
---
Override variables, create theme variants, or disable shipped CSS.
:::
:::card
---
icon: i-tabler-player-play
title: Live examples
to: https://www.toastflow.top/docs/examples/live-examples
---
Try
`show`
, typed helpers, loading toasts, updates, and store controls in the browser.
:::
:::card
---
icon: i-tabler-package
title: Runtime exports
to: https://www.toastflow.top/docs/api/runtime-exports
---
Components,
`toast`
, types, and core utilities exported by
`vue-toastflow`
.
:::
::
# Toasts
This page covers the toast payload model and how show, update, and dismiss work.
## Types
Toastflow supports seven toast types:
::card-group
:::card{icon="i-tabler-loader-2" title="loading"}
Async work in progress. Progress bar is disabled, duration is infinite.
:::
:::card{icon="i-tabler-help-circle" title="default"}
Neutral notification without a specific semantic.
:::
:::card{icon="i-tabler-circle-check" title="success"}
Positive outcome confirmation.
:::
:::card{icon="i-tabler-circle-x" title="error"}
Failure or critical issue.
:::
:::card{icon="i-tabler-info-circle" title="info"}
Informational message.
:::
:::card{icon="i-tabler-alert-triangle" title="warning"}
Warning that requires attention.
:::
:::card{icon="i-tabler-sparkles" title="custom"}
No preset colors. Use with
`theme`
or
`css`
for fully branded toasts.
:::
::
## Create Toasts
Use the call style that makes the content clearest at the call site.
::code-group
```ts [payload-object.ts]
toast.show({
type: "success",
title: "Saved",
description: "Done",
});
```
```ts [title-options.ts]
toast.show("Saved", {
type: "success",
description: "Done",
});
```
```ts [typed-helper.ts]
toast.success({
title: "Saved",
description: "Done",
});
```
::
::docs-toast-preview{variant="create"}
:::warning
`title`
or
`description`
must be non-empty. Passing both as empty strings throws.
:::
## Per-Toast Overrides
Any global config field can be overridden on a single toast:
```ts [per-toast-override.ts]
toast.info({
title: "Maintenance",
description: "At 22:00 UTC",
position: "bottom-center",
duration: 15000,
closeOnClick: true,
progressAlignment: "left-to-right",
});
```
See the full option list on the [Configuration](https://www.toastflow.top/docs/api/configuration) page.
## Positions
| Position | Anchor |
| --------------- | -------------------------- |
| `top-left` | Top-left corner |
| `top-center` | Top center |
| `top-right` | Top-right corner (default) |
| `bottom-left` | Bottom-left corner |
| `bottom-center` | Bottom center |
| `bottom-right` | Bottom-right corner |
## Ordering
`order` controls how new toasts are inserted and which toast gets evicted when the stack is full.
- **`newest`** (default) — newest toast on top, oldest evicted first
- **`oldest`** — oldest toast on top, newest evicted first
## Overflow Scroll
With `overflowScroll: true`, a stack taller than the viewport becomes scrollable (with a thin themed scrollbar) instead of overflowing off-screen. Works for every position; bottom stacks stay pinned to the newest toast unless you scroll away. Scrollbar colors are themable via the [`--tf-toast-scrollbar-*` variables](https://www.toastflow.top/docs/global/styling).
```ts [overflow-scroll.ts]
createToastflow({ overflowScroll: true });
```
## Duplicate Prevention
Enable `preventDuplicates: true` to avoid re-adding identical toasts. Matching uses `position`, `containerId`, `type`, `title`, and `description`.
When a duplicate is found:
:::steps
### Existing toast updated
New incoming overrides are merged into the existing toast.
### Id reused
The original toast id is kept.
### Timer restarted
Visible toast timer restarts with the configured duration.
### Event emitted
A `duplicate` event fires via `subscribeEvents`.
:::
## Update and Dismiss
```ts [update-and-dismiss.ts]
const id = toast.warning({
title: "Low disk space",
description: "Only 1 GB left",
});
toast.update(id, {
title: "Disk space restored",
type: "success",
});
toast.dismiss(id);
toast.dismissAll();
```
With multiple containers, `dismissAll` accepts a `{ containerId }` filter — see [Multiple containers](https://www.toastflow.top/docs/vue/multiple-containers).
## Dismiss Interactions
Besides the close button and the programmatic API, users can dismiss toasts directly:
| Interaction | Option | Default | Behavior |
| -------------- | ---------------------- | ------- | -------------------------------------------------------------------------------------------------------------- |
| Click anywhere | `closeOnClick: true` | `false` | Clicking the toast body dismisses it. Buttons and links inside the toast still work normally. |
| Swipe right | `swipeToDismiss: true` | `false` | Drag the toast to the right (pointer or touch). Past \~35% of its width it dismisses; otherwise it snaps back. |
| Escape key | always on | — | Pressing `Escape` while focus is inside the toast (e.g. on a button) dismisses it. |
```ts [swipe-to-dismiss.ts]
toast.info("Swipe me away", {
swipeToDismiss: true,
});
```
Swiping starts only after a small horizontal movement, so vertical scrolling on touch devices is not hijacked, and it never starts on interactive elements (buttons, links, inputs).
## Lifecycle Hooks
Set globally or per toast:
:::field-group
::::field{name="onMount" type="(ctx) => void"}
After the toast enters state.
::::
::::field{name="onUnmount" type="(ctx) => void"}
After the toast is removed from state.
::::
::::field{name="onClick" type="(ctx, event) => void"}
When the toast body is clicked.
::::
::::field{name="onClose" type="(ctx) => void"}
Right before the leaving phase starts.
::::
:::
Each hook receives a context object with `id`, `position`, `type`, `title`, `description`, `createdAt`, and `containerId` (when set).
## Accessibility
Accessible behavior works out of the box — nothing to configure:
- `error` and `warning` toasts render with `role="alert"` and `aria-live="assertive"`; all other types use `role="status"` and `aria-live="polite"`.
- Every toast gets an `aria-label` built from its title, description, and timestamp — HTML markup is stripped, so screen readers only hear text.
- The close button is a real `