useNotifications
Notification lifecycle management with severity levels, state mutations, toast queuing, and auto-dismiss.
Installation
Install the Notifications plugin in your app’s entry point:
import { createApp } from 'vue'
import { createNotificationsPlugin } from '@vuetify/v0'
import App from './App.vue'
const app = createApp(App)
app.use(createNotificationsPlugin())
app.mount('#app')Persistence
Pass persist: true to remember how the user interacted with each notification across reloads. Requires useStorage to be installed first. Only interaction state is stored — a map of notification id to its readAt / seenAt / archivedAt / snoozedUntil timestamps under the key notifications (the plugin namespace with the v0: prefix stripped). Notification content is never stored; your code (or an adapter) stays the source of truth.
import { createStoragePlugin, createNotificationsPlugin } from '@vuetify/v0'
app.use(createStoragePlugin())
app.use(createNotificationsPlugin({ persist: true }))Give tickets a stable id — saved state is matched by id and merges onto notifications as they register, whether that happens at load or later (adapter pushes, runtime sends). A notification that is never re-registered is not resurrected from storage, and its saved state is pruned on the next write. Expired snoozes and malformed entries are dropped on load.
Usage
Once the plugin is installed, use the useNotifications composable in any component:
<script setup lang="ts">
import { useNotifications } from '@vuetify/v0'
const notifications = useNotifications()
function onSave () {
notifications.send({
subject: 'Changes saved',
severity: 'success',
timeout: 3000,
})
}
function onError () {
notifications.send({
subject: 'Build failed',
severity: 'error',
timeout: -1,
})
}
</script>
<template>
<button @click="onSave">
Save
</button>
</template>Adapters
Adapters let you swap the underlying notification service without changing your application code.
| Adapter | Import | Description |
|---|---|---|
KnockNotificationsAdapter | @vuetify/v0/notifications | Knock↗︎ integration |
NovuNotificationsAdapter | @vuetify/v0/notifications | Novu↗︎ integration |
Knock
Knock↗︎ is a notification infrastructure platform with feeds, preferences, and multi-channel delivery. Install their JavaScript SDK↗︎ to get started. Supports both inbound (feed → notifications) and outbound (read/archive → Knock API).
pnpm add @knocklabs/clientnpm install @knocklabs/clientyarn add @knocklabs/clientbun add @knocklabs/clientimport { createApp } from 'vue'
import { createNotificationsPlugin } from '@vuetify/v0'
import { KnockNotificationsAdapter } from '@vuetify/v0/notifications'
import { feed } from './plugins/knock'
import App from './App.vue'
const app = createApp(App)
app.use(
createNotificationsPlugin({
adapter: new KnockNotificationsAdapter(feed),
})
)
app.mount('#app')import Knock from '@knocklabs/client'
export const knock = new Knock(import.meta.env.VITE_KNOCK_PUBLIC_KEY)
knock.authenticate(import.meta.env.VITE_KNOCK_USER_ID)
export const feed = knock.feeds.initialize(
import.meta.env.VITE_KNOCK_FEED_CHANNEL_ID
)Novu
Novu↗︎ is an open-source notification infrastructure with in-app feeds, digests, and multi-channel delivery. Install their JavaScript SDK↗︎ to get started. Supports both inbound (feed → notifications) and outbound (read/unread/seen/archive/unarchive → Novu API).
The adapter maps Novu severity strings to NotificationSeverity by default: critical/high → error, medium → warning, low → info. Pass a custom severity function to override.
pnpm add @novu/jsnpm install @novu/jsyarn add @novu/jsbun add @novu/jsimport { createApp } from 'vue'
import { createNotificationsPlugin } from '@vuetify/v0'
import { NovuNotificationsAdapter } from '@vuetify/v0/notifications'
import { novu } from './plugins/novu'
import App from './App.vue'
const app = createApp(App)
app.use(
createNotificationsPlugin({
adapter: new NovuNotificationsAdapter(novu),
})
)
app.mount('#app')import { Novu } from '@novu/js'
export const novu = new Novu({
subscriberId: import.meta.env.VITE_NOVU_SUBSCRIBER_ID,
applicationIdentifier: import.meta.env.VITE_NOVU_APP_ID,
})Custom Adapters
Extend NotificationsAdapter to connect any backend:
import { NotificationsAdapter } from '@vuetify/v0/notifications'
import type { NotificationsAdapterContext } from '@vuetify/v0'
class MyBackendAdapter extends NotificationsAdapter {
setup (context: NotificationsAdapterContext) {
// Wire inbound: push notifications into the registry
myBackend.onMessage(msg => {
context.send({ id: msg.id, title: msg.title, body: msg.body })
})
// Wire outbound: sync read/archive actions back to the backend
context.on('notification:read', (data: any) => {
myBackend.markRead(data.id)
})
}
dispose () {
myBackend.disconnect()
}
}
app.use(createNotificationsPlugin({ adapter: new MyBackendAdapter() }))Adapter context methods:
| Method | Purpose |
|---|---|
send(input) | Register and enqueue for toast display (real-time inbound) |
register(input) | Register in history only — no toast (initial/historical load) |
on(event, handler) | Subscribe to outbound lifecycle events |
off(event, handler) | Unsubscribe from a lifecycle event |
Custom Ticket Fields
Extend NotificationTicketInput to add domain-specific fields. Pass the type parameter through the adapter and plugin:
import { NotificationsAdapter } from '@vuetify/v0/notifications'
import type { NotificationTicketInput, NotificationsAdapterContext } from '@vuetify/v0'
interface AppNotification extends NotificationTicketInput {
priority: 'low' | 'medium' | 'high'
imageUrl?: string
}
class MyBackendAdapter extends NotificationsAdapter<AppNotification> {
setup (context: NotificationsAdapterContext<AppNotification>) {
myBackend.onMessage(msg => {
context.send({
id: msg.id,
subject: msg.title,
priority: msg.priority, // custom field
imageUrl: msg.imageUrl, // custom field
})
})
}
}
app.use(createNotificationsPlugin<AppNotification>({ adapter: new MyBackendAdapter() }))Custom fields are preserved on the ticket and accessible anywhere you inject the notifications context.
Architecture
createNotifications layers notification semantics on top of the registry and queue primitives, with plugin installation via createPluginContext:
Reactivity
API
| Method | Description |
|---|---|
send(input) | Create notification + enqueue for toast display |
register(input) | Create notification in registry only (no toast). Use for historical items |
queue | Queue context — queue.values(), queue.pause(), queue.resume() |
read(id) / unread(id) | Toggle read state |
seen(id) | Mark as seen |
archive(id) / unarchive(id) | Toggle archive state |
snooze(id, until) / wake(id) | Snooze with expiry |
readAll() / archiveAll() | Bulk operations |
onboard(items) | Bulk-register enriched notifications into registry (no toast) |
clear() | Remove all notifications from the registry |
dispose() | Tear down event listeners and clear the registry |
Examples
Click Simulate Event to push notifications
Recipes
Severity Levels
The severity field categorizes notifications by urgency. It maps to ARIA live region roles automatically:
| Value | ARIA role | Use for |
|---|---|---|
'error' | role="alert" | Failures, errors, destructive outcomes |
'warning' | role="alert" | Degraded state, approaching limits |
'info' | role="status" | Neutral updates, background activity |
'success' | role="status" | Completed actions, positive outcomes |
NotificationSeverity is extensible — custom values like 'critical' are accepted with autocomplete for the four defaults.
FAQ
send() registers a notification and enqueues it for toast display — use it for real-time, in-the-moment events. register() adds it to the registry only, with no toast, which is what you want when loading historical or initial notifications.
Pass persist: true to createNotificationsPlugin, and install createStoragePlugin first. The saved value is a map of interaction state per notification id (readAt, seenAt, archivedAt, snoozedUntil) — never notification content. On load it merges onto notifications as your code or adapter registers them; nothing is created from storage.
Pass timeout: -1 on send(). A positive timeout (e.g. 3000) auto-dismisses after that many milliseconds; -1 makes it persist until dismissed explicitly.
They’re independent. seen drives the unseen badge count (cleared when the inbox is opened); read drives visual weight (cleared when the user actually reads it) — mirroring how GitHub and Slack distinguish the two.
Pass the matching adapter to createNotificationsPlugin — KnockNotificationsAdapter or NovuNotificationsAdapter from @vuetify/v0/notifications. Both handle inbound (feed → registry) and outbound (read/archive → service) sync. For any other backend, extend NotificationsAdapter and wire its setup(context).