<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Adrián Janočko</title>
    <description>The latest articles on DEV Community by Adrián Janočko (@adrianjanocko).</description>
    <link>https://dev.to/adrianjanocko</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3659035%2F7217c910-bdfe-4234-af9b-0d95ae03a59a.png</url>
      <title>DEV Community: Adrián Janočko</title>
      <link>https://dev.to/adrianjanocko</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/adrianjanocko"/>
    <language>en</language>
    <item>
      <title>Tired of Vue toast libraries, so I built my own (headless, Vue 3, TS-first)</title>
      <dc:creator>Adrián Janočko</dc:creator>
      <pubDate>Fri, 12 Dec 2025 20:06:50 +0000</pubDate>
      <link>https://dev.to/adrianjanocko/tired-of-vue-toast-libraries-so-i-built-my-own-headless-vue-3-ts-first-1h8h</link>
      <guid>https://dev.to/adrianjanocko/tired-of-vue-toast-libraries-so-i-built-my-own-headless-vue-3-ts-first-1h8h</guid>
      <description>&lt;p&gt;Hey folks 👋 author here, looking for feedback.&lt;/p&gt;

&lt;p&gt;I recently needed a toast system for a Vue 3 app that was:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;modern,&lt;/li&gt;
&lt;li&gt;lightweight,&lt;/li&gt;
&lt;li&gt;and didn’t fight my custom styling.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;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.&lt;/p&gt;

&lt;p&gt;So I ended up building my own: &lt;strong&gt;Toastflow&lt;/strong&gt; (core engine) + &lt;strong&gt;vue-toastflow&lt;/strong&gt; (Vue 3 renderer).&lt;/p&gt;

&lt;h2&gt;
  
  
  What it is
&lt;/h2&gt;

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

&lt;h2&gt;
  
  
  Quick taste
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;    &lt;span class="c1"&gt;// main.ts&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createApp&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;App&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;./App.vue&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;createToastflow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToastContainer&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-toastflow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;createApp&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;App&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;use&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
      &lt;span class="nf"&gt;createToastflow&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="c1"&gt;// optional global defaults&lt;/span&gt;
        &lt;span class="na"&gt;position&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;top-right&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;duration&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;5000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;}),&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="c1"&gt;// register globally or import locally where you render it    &lt;/span&gt;
    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;component&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;ToastContainer&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;ToastContainer&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

    &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;#app&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight vue"&gt;&lt;code&gt;    &lt;span class="c"&gt;&amp;lt;!-- Somewhere in your app --&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt; &lt;span class="na"&gt;setup&lt;/span&gt; &lt;span class="na"&gt;lang=&lt;/span&gt;&lt;span class="s"&gt;"ts"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;toast&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;vue-toastflow&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;

    &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;handleSave&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;toast&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;success&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Saved&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Your changes have been stored.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;})&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;script&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;

    &lt;span class="nt"&gt;&amp;lt;&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;button&lt;/span&gt; &lt;span class="err"&gt;@&lt;/span&gt;&lt;span class="na"&gt;click=&lt;/span&gt;&lt;span class="s"&gt;"handleSave"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;Save&lt;span class="nt"&gt;&amp;lt;/button&amp;gt;&lt;/span&gt;
      &lt;span class="nt"&gt;&amp;lt;ToastContainer&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/&lt;/span&gt;&lt;span class="k"&gt;template&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Links
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Playground / demo: &lt;a href="https://toastflow.adrianjanocko.sk" rel="noopener noreferrer"&gt;https://toastflow.adrianjanocko.sk&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GitHub: &lt;a href="https://github.com/adrianjanocko/toastflow" rel="noopener noreferrer"&gt;https://github.com/adrianjanocko/toastflow&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;npm (Vue renderer): &lt;a href="https://www.npmjs.com/package/vue-toastflow" rel="noopener noreferrer"&gt;https://www.npmjs.com/package/vue-toastflow&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>vue</category>
      <category>toast</category>
      <category>webdev</category>
      <category>opensource</category>
    </item>
  </channel>
</rss>
