Notification Center
Accessible, unstyled notification feed and inbox that can record toast history.
| Package | @vizejs/ui/notification-center |
| Maturity | stable |
| Own the source | vize lib pull notification-center |
| Requires | context, controllable-state, id |
| Aliases | notification center, inbox, activity feed, toast history, notifications |
| Covers | WAI-ARIA Feed pattern, Mantine Notifications, Novu Inbox, Sonner toast history recipes |
Usage
import { NotificationCenter, NotificationCenterRoot, NotificationCenterEmpty, NotificationCenterItem, NotificationCenterList, NotificationCenterTrigger } from "@vizejs/ui/notification-center";
Or copy the source into your project with vize lib pull notification-center (see Source Distribution).
API
NotificationCenter<Data = unknown>
Source: src/families/feedback/notification-center/notification-center-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
store |
NotificationStore<Data> |
undefined |
External store, for example one shared with a router guard or fed by connectNotificationSource. undefined creates a store per root. |
initial |
readonly NotificationInput<Data>[] |
undefined |
Notifications seeded into the root-owned store. |
maxLength |
number |
100 |
History limit of the root-owned store. |
now |
() => number |
Date.now |
Clock of the root-owned store; inject one for deterministic SSR. |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
label |
string |
"Notifications" |
Accessible name of the feed and base label of the trigger. |
inline |
boolean |
false |
Render the feed permanently (inbox page) instead of as a disclosure. |
open |
boolean |
undefined |
Controlled disclosure state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial disclosure state for uncontrolled use. |
Events
| Event | Payload | Description |
|---|---|---|
update:open |
[value: boolean] |
Fired when the disclosure requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
NotificationCenterSlotState |
Trigger, list, and empty-state children. |
Exposed
| Member | Type | Description |
|---|---|---|
count |
— | |
listId |
— | |
open |
— | |
setOpen |
— | |
state |
— | |
store |
— | |
unreadCount |
— |
NotificationCenterRoot<Data = unknown>
Source: src/families/feedback/notification-center/notification-center-root.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
store |
NotificationStore<Data> |
undefined |
External store, for example one shared with a router guard or fed by connectNotificationSource. undefined creates a store per root. |
initial |
readonly NotificationInput<Data>[] |
undefined |
Notifications seeded into the root-owned store. |
maxLength |
number |
100 |
History limit of the root-owned store. |
now |
() => number |
Date.now |
Clock of the root-owned store; inject one for deterministic SSR. |
id |
string | null |
undefined |
Consumer-owned base id. null and undefined select a deterministic fallback. |
label |
string |
"Notifications" |
Accessible name of the feed and base label of the trigger. |
inline |
boolean |
false |
Render the feed permanently (inbox page) instead of as a disclosure. |
open |
boolean |
undefined |
Controlled disclosure state. undefined selects uncontrolled behavior. |
defaultOpen |
boolean |
false |
Initial disclosure state for uncontrolled use. |
Events
| Event | Payload | Description |
|---|---|---|
update:open |
[value: boolean] |
Fired when the disclosure requests a controlled open value. |
open-change |
[value: boolean, previous: boolean, nativeEvent: Event | null] |
Fired after any distinct open-state request. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
NotificationCenterSlotState |
Trigger, list, and empty-state children. |
Exposed
| Member | Type | Description |
|---|---|---|
open |
boolean |
Whether the list is shown (always true for inline centers). |
state |
NotificationCenterState |
Stable state token. |
unreadCount |
number |
Unread active notifications. |
count |
number |
Active notifications. |
store |
NotificationStore<Data> |
The store driving this center. |
listId |
string |
Id of the feed element. |
setOpen |
(value: boolean, event?: Event | null) => boolean |
Request an open value. Returns whether it changed. |
NotificationCenterEmpty
Source: src/families/feedback/notification-center/notification-center-empty.vue
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
— | Empty-state contents, shown only while no active notification exists. |
NotificationCenterItem<Data = unknown>
Source: src/families/feedback/notification-center/notification-center-item.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
notification (required) |
NotificationRecord<Data> |
required |
Notification snapshot to render. |
position (required) |
number |
required |
One-based position in the feed (aria-posinset). |
setSize (required) |
number |
required |
Feed size (aria-setsize); -1 when unknown. |
markReadOnActivate |
boolean |
true |
Mark the notification read when the article itself is clicked or receives Enter. |
Events
| Event | Payload | Description |
|---|---|---|
activate |
[notification: NotificationRecord<Data>, nativeEvent: Event] |
Fired when the article is activated by click or Enter. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
NotificationCenterItemSlotState<Data> |
Article contents. Put titleId/descriptionId on the visible headline and text. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
HTMLElement | null |
Rendered article. |
NotificationCenterList
Source: src/families/feedback/notification-center/notification-center-list.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
busy |
boolean |
false |
Whether the feed is loading or inserting articles (aria-busy). |
markReadOnActivate |
boolean |
true |
Default for rendered items: mark a notification read when its article is activated. |
closeOnEscape |
boolean |
true |
Let Escape inside a disclosure-style feed close it and refocus the trigger. |
Slots
| Slot | Slot props | Description |
|---|---|---|
item |
NotificationCenterListItemSlotProps |
Render one notification, normally with NotificationCenterItem. |
default |
— | Content after the articles, for example an empty state or a load-more button. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
HTMLElement | null |
Rendered feed element. |
focusItem |
(index: number) => boolean |
Focus the article at a zero-based index. Returns whether it moved focus. |
NotificationCenterTrigger
Source: src/families/feedback/notification-center/notification-center-trigger.vue
Props
| Prop | Type | Default | Description |
|---|---|---|---|
disabled |
boolean |
false |
Remove the trigger from activation and sequential keyboard focus. |
formatLabel |
(label: string, unreadCount: number) => string |
|
Build the accessible name from the root label and unread count. |
Events
| Event | Payload | Description |
|---|---|---|
click |
[nativeEvent: MouseEvent] |
Fired before the trigger toggles the center. Call preventDefault() to keep state unchanged. |
Slots
| Slot | Slot props | Description |
|---|---|---|
default |
NotificationCenterSlotState |
Trigger contents, typically an icon and an unread badge. |
Exposed
| Member | Type | Description |
|---|---|---|
element |
HTMLButtonElement | null |
Rendered native button. |
focus |
(options?: FocusOptions) => void |
Move focus to the trigger. |
Behavior
Normative behavior for @vizejs/ui/notification-center: an SSR-safe notification history
store and an APG feed inbox. It can serve
as toast history by feeding it through connectNotificationSource. Every row is proven by
the named test.
| State x input | Observable outcome | Proven by |
|---|---|---|
store add |
Notifications are frozen snapshots, newest first, with "{idPrefix}-{n}" ids and timestamps from the injected now. |
adds notifications newest first with generated ids and injected timestamps |
store add with an existing id |
The notification is replaced in place. | re-adding an id replaces it in place |
markRead/markUnread/update/archive/remove |
Each reports whether anything changed; archived notifications leave the feed and the unread count. | read, archive, update, and remove report whether anything changed |
markAllRead |
Only active unread notifications change; the count is returned. | markAllRead only touches active unread notifications |
groups |
Active notifications grouped by key in first-appearance order with unread counts. | groups preserve first appearance order with per-group unread counts |
maxLength exceeded |
The oldest notifications are evicted. | maxLength evicts the oldest notifications |
initial, clear |
Seeds are sorted by createdAt; clear empties the store. |
initial notifications and clear |
connectNotificationSource |
Source emissions are added until the returned function unsubscribes. | connectNotificationSource feeds the store until unsubscribed |
| invalid option or type | Throws VIZE_UI_NOTIFICATION_OPTION. |
invalid options and inputs throw diagnostics |
| disclosure trigger click | The trigger toggles aria-expanded, controls the feed id, names the unread count, and the feed stops being hidden. |
trigger discloses the feed and announces the unread count |
| rendered feed | role="feed" with aria-label/aria-busy; articles are focusable with aria-posinset, aria-setsize, title/description wiring, data-read, data-type, data-group; inline centers omit aria-expanded. |
feed renders APG articles with position, size, labels, and read state |
| PageDown / PageUp inside an article | Focus moves to the next or previous article; at the edges focus stays and the key is not consumed. | PageDown and PageUp move focus between articles |
| click or Enter on an article | The notification is marked read and activate fires unless markReadOnActivate is false; clicks on inner controls are ignored. |
activating an article marks it read unless opted out |
| Escape inside a disclosure feed | The feed closes and focus returns to the trigger. | Escape closes a disclosure feed and returns focus to the trigger |
custom item slot |
Consumers render NotificationCenterItem with typed data and receive ids plus markRead/archive/remove actions. |
custom item slots receive typed actions and ids |
| root-owned store | The root creates, exposes, and injects its store; useNotificationCenter() reads it; the empty state shows when nothing remains. |
root owns a store, exposes it, and useNotificationCenter reads it |
| parts or composable outside the root | Throws VIZE_UI_CONTEXT_MISSING. |
parts and the composable require a NotificationCenterRoot |
| SSR | Isolated requests render byte-identical feeds with deterministic ids. | renders byte-identical feed markup across isolated SSR requests |
| hydration | The feed hydrates without warnings or node replacement. | hydrates the feed without diagnostics |
| public types | Data flows through records, slots, expose, and useNotificationCenter<Data>(); types are closed unions. |
src/families/feedback/notification-center/notification-center.types.test-d.ts |
Components
| Component | State x input | Outcome |
|---|---|---|
notification-center-root.vue |
store, inline, open |
Creates or adopts the store, owns disclosure state, provides context and the store. |
notification-center-trigger.vue |
click | Toggles the feed and labels itself with the unread count. |
notification-center-list.vue |
notifications, PageUp/PageDown, Esc | Renders the role="feed" element and one article per active notification. |
notification-center-item.vue |
click, Enter | Renders one article with feed positions and marks it read on activation. |
notification-center-empty.vue |
no active notifications | Shows the empty-state text; hidden otherwise. |
Parts And Data
| Target | Public contract |
|---|---|
| Root | data-vize-ui="notification-center-root", part="root", data-state, data-inline, data-unread-count |
| Trigger | data-vize-ui="notification-center-trigger", part="trigger", data-unread-count, data-has-unread |
| List | data-vize-ui="notification-center-list", part="list", role="feed", data-count |
| Item | data-vize-ui="notification-center-item", part="item", data-read, data-type, data-group |
| Empty | data-vize-ui="notification-center-empty", part="empty" |
The feed pattern's Control+Home / Control+End exits are left to the surrounding page. NotificationCenter ships no stylesheet.