Adrián Janočko

Tired of Vue toast libraries, so I built my own (headless, Vue 3, TS-first)

by•

Hey folks đź‘‹ author here, looking for feedback.

I recently needed a toast system for a Vue 3 app that was:
• modern
• lightweight
• and didn’t fight my custom styling

I tried several Vue toast libraries and kept hitting the same issues: a lot of them were Vue 2–only or basically unmaintained, the styling was hard-wired instead of properly themeable, some were missing pretty basic options, and almost none gave me predictable behavior for things like duplicates, timers, or multiple stacks.

So I ended up building my own: Toastflow (core engine) + vue-toastflow (Vue 3 renderer).

WHAT IT IS?
• Headless toast engine + Vue 3 renderer
Toastflow keeps state in a tiny, framework-agnostic store (toastflow-core), and vue-toastflow is just a renderer on top with <ToastContainer /> plus a global toast helper.
• CSS-first theming
The default look is driven by CSS variables (including per-type colors like --success-bg, --error-text, etc.). You can swap the design by editing one file or aligning it with your Tailwind/daisyUI setup.
• Smooth stack animations
Enter/leave + move animations when items above/below are removed, for all positions (top-left, top-center, top-right, bottom-left, bottom-center, bottom-right). Implemented with TransitionGroup and overridable via animation config.
• Typed API, works inside and outside components
You install the plugin once, then import toast from anywhere (components, composables, services, plain TS modules). Typed helpers: toast.show, toast.success, toast.error, toast.warning, toast.info, toast.loading, toast.update, toast.dismiss, toast.dismissAll, etc.
• Deterministic behavior
The core handles duplicates, timers, pause-on-hover, close-on-click, maxVisible, stack order (newest/oldest), and clear-all in a predictable way.
• Extras
Promise/async flows (toast.loading), optional HTML content with supportHtml, lifecycle hooks, events (toast.subscribeEvents), timestamps (showCreatedAt, createdAtFormatter), and a headless slot API if you want to render your own card.

QUICK TASTE

import { createApp } from 'vue'
import App from './App.vue'
import { createToastflow, ToastContainer } from 'vue-toastflow'

const app = createApp(App)

app.use(
  createToastflow({
    // optional global defaults
    position: 'top-right',
    duration: 5000,
  }),
)

// register globally or import locally where you render it
app.component('ToastContainer', ToastContainer)

app.mount('#app')


Somewhere in your app (single-file component):

<script setup lang="ts">
import { toast } from 'vue-toastflow'

function handleSave() {
  toast.success({
    title: 'Saved',
    description: 'Your changes have been stored.',
  })
}
</script>

<template>
  <button @click="handleSave">Save</button>
  <ToastContainer />
</template>
8 views

Add a comment

Replies

Be the first to comment