Open-source project
smastrom/notivue avatar
smastrom/notivue

notivue: a headless toast system for Vue where the components are optional

Zero-dependency, powerful, and truly customizable toast notification system for Vue

867 stars13 forksTypeScriptMIT

At a glance

What is it?
Notivue is a toast notification system for Vue and Nuxt with no runtime dependencies, a headless API that hands your own component the state, and optional add-ons for swiping and keyboard control. The pieces are importable separately, and the release history is a story of a library whose code keeps moving after its last tag.
Who is it for?
Adopt notivue if you are on Vue or Nuxt and want a notification system you can restyle entirely with your own components, since the headless slot API exposes the item state and the built-in components are optional. Do not adopt it if you need a package with a current release trail you can pin, because the newest tag listed is v2.4.5 from 2024-09-01 while the last push was on 2026-06-12, so published versions and current code are not the same thing.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 137 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 25, 2026, and from our analysis. They are not legal advice.

Editorial analysis

Zero dependencies, and an installation that is one line

Notivue is a toast notification system for Vue and Nuxt, written in TypeScript and MIT licensed. The repository description calls it zero-dependency, and the feature list puts fully modular with zero dependencies first, with the note that you can granularly include only the features you need.

That claim is about the runtime, and it matters for a component library that is added to an application rather than installed once at the platform level. A toast system is a small feature that every team ends up writing, and a library that brings nothing with it adds nothing to your bundle except itself.

Installation is the usual package manager incantation, with the alternatives listed as comments:

shell
pnpm add notivue

# npm i notivue
# yarn add notivue
# bun i notivue

The setup in a Vite project is a plugin, two optional stylesheets and the app mount. The createNotivue function takes the options and returns a plugin, and the stylesheet imports carry comments explaining when they are actually needed:

ts
import { createApp } from 'vue'
import { createNotivue } from 'notivue'

import App from './App.vue'

import 'notivue/notification.css' // Only needed if using built-in <Notification />
import 'notivue/animations.css' // Only needed if using default animations

const notivue = createNotivue(/* Options */)
const app = createApp(App)

app.use(notivue)
app.mount('#app')

The comments are worth reading as documentation. The notification stylesheet is only required if you use the built-in Notification component, and the animation stylesheet only if you use the default animations. Both are therefore optional if you go the headless route, which is the point of a modular library: you are not forced to take a stylesheet whose rules will collide with your own design system.

The project is published from a pnpm monorepo with a packages/ directory, a playground/, an astro-playground/ and a tests/ directory, and the docs are at docs.notivue.smastrom.io with a live demo at notivue.smastrom.io.

The headless API hands your component the whole state

The core idea is that Notivue manages the notification lifecycle and your component renders it. The built-in template does exactly that with a scoped slot:

vue
<script setup>
import { Notivue, Notification, push } from 'notivue'
</script>

<template>
  <button @click="push.success('Hi! I am your first notification!')">Push</button>

  <Notivue v-slot="item">
    <Notification :item="item" />
  </Notivue>
</template>

The push function is the entry point from anywhere in the app, and the success variant is only one of a set of types, since the library describes themes and icons for the built-in notifications.

The headless variant replaces the Notification component with your own markup, and the slot exposes an item object with the message and the accessibility attributes already computed for you:

vue
<script setup>
import { Notivue, push } from 'notivue'
</script>

<template>
  <button @click="push.success('Hi! I am your first notification!')">Push</button>

  <Notivue v-slot="item">
    <!-- Your notification 👇 -->
    <div class="rounded-full flex py-2 pl-3 bg-slate-700 text-slate-50 text-sm">
      <p :role="item.ariaRole" :aria-live="item.ariaLive" aria-atomic="true">
        {{ item.message }}

Note what the library computes and what you write. The role and the live region come from the item, so the announcement behaviour is not something each team has to get right, and the dismiss button calls item.clear with the library owning the removal. What you write is the markup, the styling and the icon.

The design decision here is the usual one for a headless library, and it is a good one: the parts that are easy to get subtly wrong, in this case screen reader announcements and dismissal, stay with the library, while the parts that are a matter of taste stay with you.

Accessibility is carried on the item, not bolted on afterwards

The feature list claims full accessibility with built-in announcements, reduced-motion support and keyboard support, and the code above shows what that means concretely.

Each notification item carries an ariaRole and an ariaLive value, and the example applies them to the paragraph containing the message, together with aria-atomic. A live region is how a screen reader is told that new content appeared without the user moving focus, and the choice of role and politeness is exactly the kind of detail that is easy to leave out of a hand-rolled toast. Because the values come from the item, a custom component inherits that behaviour as long as it binds them, which is the right place for the responsibility to sit.

The dismiss control in the example has an aria-label, which is the other half of the same problem: an icon-only button that says nothing is invisible to a screen reader user.

Reduced motion is handled at the library level. Notivue ships default animations, and the animation stylesheet is optional, so a team that respects reduced motion can skip the default animations or replace them with their own CSS classes. The feature list also mentions that transitions and animations are customised with CSS classes, which is the same idea: the library provides a default, and the animation surface is plain CSS rather than a JavaScript animation engine.

Keyboard support is a separate opt-in component, which is worth understanding as a design point rather than an oversight. Keyboard dismissal of a toast is a behaviour with opinions about focus, and the library has moved it into a component you choose to add, alongside the swipe component for touch dismissal.

Swipe and keyboard behaviour are separate components

Two drop-in components are named in the feature list: NotivueSwipe and NotivueKeyboard, both described as optional and customisable. That word optional carries the design.

A swipe-to-dismiss gesture on a toast is not free. It has to coexist with scrolling, with a page that is being dragged, and with users who have reduced motion switched on or who use a keyboard instead of a pointer. Making it a separate component means a project that does not want gesture dismissal does not get the event listeners at all, and a project that does want it opts in knowingly.

The same applies to the keyboard component. It brings a keyboard control scheme that the library will otherwise leave alone, and a team that has its own global shortcuts may prefer to bind them itself.

The built-in notifications are described as beautiful and ready-made, with themes, icons, a progress bar and native RTL support. A progress bar on a notification is a timing affordance, and RTL support matters more than it sounds for a component that renders text and icons in a layout, since it means the built-in components are not laid out assuming a left-to-right language.

The dynamic notifications feature is the remaining piece of the list: pending notifications can be updated in place, so a progress that starts at 10 percent and ends at 90 percent updates one notification rather than pushing three. For long-running operations that is the difference between one toast and a queue of stale ones.

Two credits in the README are worth recording for anyone thinking about the visuals: the icons come from the Ionic team, and the animated spinner is by Uktash Verna.

Nuxt and Astro have their own modules

The framework integration is the part that decides how much setup you do, and both Nuxt and Astro are covered.

For Nuxt, the module is registered in the configuration and the stylesheets go into the css array, with the same only-needed comments as in the Vite case:

ts
export default defineNuxtConfig({
  modules: ['notivue/nuxt'],
  css: [
    'notivue/notification.css', // Only needed if using built-in <Notification />
    'notivue/animations.css' // Only needed if using default animations
  ],
  notivue: {
    // Options
  }
})

The options live under a notivue key in the Nuxt config rather than being passed to a factory call, which is what a module should do: the configuration is declared once, in the place the framework already reads configuration from. The component usage in the Nuxt app file is the same as in the plain Vue case, with the Notivue component and the built-in Notification inside it, and the router view or layout underneath.

Astro is handled by a built-in ad-hoc module, and the README links runnable examples for three setups on StackBlitz: a custom components example on Vite, a Nuxt example, and an Astro example. Those are the fastest way to see the three shapes of the same API, and the fact that they exist as editable projects rather than screenshots says something about how the author expects the library to be evaluated.

The Nuxt and Astro support is also why the topics list nuxt and nuxt3 alongside vue, vue3 and vuejs. The library is not a Vue component that happens to work in Nuxt, it ships framework modules for the two meta-frameworks people actually build applications with.

A monorepo whose last tag is two years old

The repository is a pnpm workspace with a private root package named notivue-monorepo, pinned to pnpm 8.14.3 and requiring Node 20 or newer. The packages/ directory holds the actual library, the playground/ and astro-playground/ directories are development sandboxes for the Vite and Astro setups, and tests/ holds both the unit tests and the browser tests.

The scripts show the workflow. The dev script builds the package and then runs the package watcher alongside the playground, and there is a separate dev:astro script for the Astro sandbox. The test script builds the package, installs, and runs the test project, while test:unit skips the build and test:gui runs the browser tests against a watching build. Formatting is enforced by prettier through lint-staged, with a separate plugin for Astro files, and husky installs the git hooks.

The maintenance picture needs two dates to be read correctly. The last push was on 2026-06-12, so the code is being touched. The newest release listed is v2.4.5 from 2024-09-01, with v2.4.4 and the v2.4.0 to v2.4.3 range in April 2024. So the repository is alive while the published version trail has not moved for a long time.

That has a direct consequence for how you adopt it. If you install from npm, you get the released version, and the features described in the README, including the Nuxt and Astro modules, are documented against the current source rather than a specific tag. If you want the current behaviour, you are tracking the repository, and you should read the changelog for whatever sits between your installed version and the code you are looking at, because the README itself points to a changelog for breaking changes in a previous version.

For a component library of this size, that is a manageable risk. For a design system that standardises on the notification component across a large product, it is a question to settle before the first release that depends on it.

Editorial conclusion

Adopt notivue if you are on Vue or Nuxt and want a notification system you can restyle entirely with your own components, since the headless slot API exposes the item state and the built-in components are optional. Do not adopt it if you need a package with a current release trail you can pin, because the newest tag listed is v2.4.5 from 2024-09-01 while the last push was on 2026-06-12, so published versions and current code are not the same thing. Before you upgrade, read the changelog between the version you use and the one you are moving to, and check the CSS import rules, because the notification and animation stylesheets are only needed when you use the built-in components or the default animations.

Frequently asked questions

How do I install notivue and show a first notification?

The README gives pnpm add notivue, with npm, yarn and bun variants as comments. In a Vite project you create the plugin with createNotivue, register it with app.use(notivue), import the Notivue and Notification components, and push a message with push.success.

What does the headless API in notivue give me?

The Notivue component exposes the current notification through a scoped slot, and the item carries the message, the computed ariaRole and ariaLive values, and a clear method for dismissal. You render the markup yourself, while the library keeps ownership of the notification lifecycle and the announcement behaviour.

Do I need to import the notivue stylesheets?

Only in the cases the comments in the README name: notivue/notification.css is needed if you use the built-in Notification component, and notivue/animations.css is needed if you use the default animations. A project using its own components can skip both.

How do I use notivue in a Nuxt project?

Add notivue/nuxt to the modules array in nuxt.config.ts, put the stylesheets in the css array, and pass your options under a notivue key. The component usage is the same as in a plain Vue app, with the Notivue component and the Notification inside it.

Is notivue actively released?

The last push to the repository was on 2026-06-12, but the newest release listed is v2.4.5 from 2024-09-01, with earlier versions in April 2024. The code is being touched while the published version trail has not moved, so read the changelog before upgrading past the version you use.

Official sources

  1. License: MIT
  2. Project website
  3. README
  4. Releases
  5. smastrom/notivue on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/smastrom-notivue.svg)](https://hysenlabs.com/projects/smastrom-notivue)

Was this useful?