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