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')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>Architecture
createNotifications layers notification semantics on top of the registry and queue primitives, with plugin installation via createPluginContext:
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.
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
Notification Center
A single createNotifications instance powering four notification surfaces through the data.type field:
| Surface | Type | Behavior |
|---|---|---|
| Banner | 'banner' | Persistent, dismissible, max 1 visible. System announcements, trial expiry |
| Toast | 'toast' | Auto-dismissing via timeout. Action feedback: “Changes saved” |
| Inline | 'inline' | Contextual, embedded in page content. Rate limits, degraded service |
| Inbox | 'inbox' or none | Full lifecycle — read, archive, snooze. Collaboration, CI alerts |
The data bag drives routing — the composable doesn’t care how notifications render. Each surface filters items by data.type.
| File | Role |
|---|---|
context.ts | Wraps createNotifications with createContext for provide/inject |
NotificationProvider.vue | Renders all surfaces: banners, inbox dropdown, snackbar stack |
NotificationConsumer.vue | Triggers notifications — simulates real app events |
inbox.vue | Entry point wiring provider and consumer |
Click Simulate Event repeatedly to cycle through banner, snackbar, and inbox notifications. Open the Inbox to interact with read/archive/snooze. Notice how seen (badge count) and read (visual weight) are distinct — mirroring GitHub and Slack.
Click Simulate Event to push notifications
Adapters
Adapters let you swap the underlying notification service without changing your application code.
| Adapter | Import | Description |
|---|---|---|
createKnockAdapter | @vuetify/v0/notifications | Knock↗ integration |
createNovuAdapter | @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 { createKnockAdapter } from '@vuetify/v0/notifications'
import { feed } from './plugins/knock'
import App from './App.vue'
const app = createApp(App)
app.use(
createNotificationsPlugin({
adapter: createKnockAdapter(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 { createNovuAdapter } from '@vuetify/v0/notifications'
import { novu } from './plugins/novu'
import App from './App.vue'
const app = createApp(App)
app.use(
createNotificationsPlugin({
adapter: createNovuAdapter(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
Implement NotificationsAdapterInterface to connect any backend:
import type { NotificationsAdapterInterface, NotificationsAdapterContext } from '@vuetify/v0'
class MyBackendAdapter implements NotificationsAdapterInterface {
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 NotificationInput to add domain-specific fields. Pass the type parameter through the adapter and plugin:
import type { NotificationInput, NotificationsAdapterInterface, NotificationsAdapterContext } from '@vuetify/v0'
interface AppNotification extends NotificationInput {
priority: 'low' | 'medium' | 'high'
imageUrl?: string
}
class MyBackendAdapter implements NotificationsAdapterInterface<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.
Functions
createNotifications
(options?: NotificationsOptions) => NotificationsContext<NotificationInput, NotificationTicket<NotificationInput>>createNotificationsContext
<_E>(_options?: NotificationsPluginOptions | undefined) => ContextTrinity<_E>createNotificationsPlugin
(_options?: NotificationsPluginOptions | undefined) => PluginuseNotifications
<_E>(namespace?: string) => _EOptions
Properties
queue
QueueContext<QueueTicketInput<unknown>, QueueTicket<QueueTicketInput<unknown>>>The toast display queue. Access `queue.values()`, `queue.pause()`, `queue.resume()`.
Methods
move
(id: ID, toIndex: number) => E | undefinedSeek for a ticket based on direction and optional predicate
seek
(direction?: "first" | "last", from?: number, predicate?: (ticket) => boolean) => E | undefinedon
<K extends Extensible<RegistryEventName>>(event: K, cb: EventHandler<E, K>) => voidListen for registry events
off
<K extends Extensible<RegistryEventName>>(event: K, cb: EventHandler<E, K>) => voidStop listening for registry events
emit
<K extends Extensible<RegistryEventName>>(event: K, data: EventPayload<E, K>) => voidEmit an event with data
batch
<R>(fn: () => R) => RExecute operations in a batch, deferring cache invalidation and event emission until complete