An in-app inbox: the bell with an unread badge, a panel (popover on desktop, sheet on phones) with filter tabs, a feed grouped by day, read/unread, inline actions and “Mark all as read”. The kit owns the UI; your adapter owns the transport.
Installation
The whole kit as one package — get updates by bumping the version. Add the styles to globals.css: @import "@vamos-ui/react/styles.css";
Usage
import { NotificationCenter, type NotificationAdapter } from "@vamos-ui/react";
const inbox: NotificationAdapter = {
list: ({ cursor, filter, signal }) => api.get("/notifications", { cursor, filter, signal }),
unreadCount: () => api.get("/notifications/unread-count"),
markRead: (ids) => api.post("/notifications/read", { ids }),
onAction: (id, action) => api.post(`/notifications/${id}/${action}`),
subscribe: (cb) => events.on("notification", cb),
};
<NotificationCenter adapter={inbox} settingsHref="/settings/notifications" toastOnNew />Pages of 20 with a “Load more” button; no infinite scroll by default.
Per-category delivery preferences (email / in-app) belong on a settings page, not in this component.
Examples
Inline actions
actions render as buttons in the item; onAction shows a spinner and removes them on success. NotificationItem also works on its own.
Live updates
subscribe() pushes new items: they prepend, the badge count grows, toastOnNew shows a toast and a polite live region announces at most once per 10 seconds.
Full page
Build a “/notifications” page from NotificationItem and Pagination.
Notifications
4 unread
Hannah Lindqvist mentioned you in “Q4 pricing”
@Ada can you double-check the enterprise tier before Friday?
CRM
New comment awaiting moderation
“Great guide — do you have a template for allergen labels?” on QR menus for restaurants
Site
Leave request from Mert Aydın
Nov 24 – Nov 28 · 5 days annual leave · 9 days left after this
People
Payment failed for invoice INV-2026-0412
Card ending 4242 was declined. We'll retry tomorrow at 09:00.
Work
Multi-app inbox
Shared across apps: items carry an app badge, tabs pass any filter to your adapter.
API
NotificationCenter
| Prop | Type | Default |
|---|---|---|
adapterlist, unreadCount, markRead, onAction?, subscribe?. | NotificationAdapter | — |
tabsFilter buttons; filter is passed to adapter.list. | { id, label, filter? }[] | All · Unread · Mentions |
toastOnNewToast when a live item arrives. | boolean | (n) => boolean | false |
pollIntervalms between unread-count refreshes without subscribe. | number | — |
size380 / 440 px panel. | "md" | "lg" | "md" |
settingsHref“Notification settings” link in the footer. | string | — |
open / defaultOpen / onOpenChangePanel state. | boolean | — |
childrenCustom composition of Trigger, Panel, Tabs, List, Footer. | ReactNode | — |
labelsOverride built-in strings (notifications.*). | Partial<NotificationLabels> | — |
AppNotification
| Prop | Type | Default |
|---|---|---|
id / title / bodyContent. | string / ReactNode | — |
createdAt / readAtRelative time via Intl.RelativeTimeFormat; readAt null = unread. | Date | string | — |
actor / icon / toneAvatar, or a tinted icon well. | { name, avatar? } / ReactNode / tone | — |
hrefClick marks read, navigates and closes the panel. | string | — |
app / categoryApp badge; tab filtering. | string | — |
actionsInline buttons (Approve / Reject). | { id, label, variant? }[] | — |
importantTurns the bell badge red while unread. | boolean | — |
NotificationItem
| Prop | Type | Default |
|---|---|---|
notificationThe item. | AppNotification | — |
onOpen / onMarkReadLink click; the ✓ button. | () => void | — |
onActionInline action. | (actionId) => void | Promise | — |
pendingActionSpinner on that action. | string | — |
posinset / setsizeFeed position (aria). | number | — |
Parts & helpers
| Prop | Type | Default |
|---|---|---|
NotificationTriggerBell + badge (99+); aria-label “Notifications, 3 unread”. | component | — |
NotificationPanelPopover ≥768px, end Sheet below; titled “Notifications”. | component | — |
NotificationTabs · NotificationList · NotificationFooterFilters, grouped feed (Today / Yesterday / Earlier), mark-all + settings. | component | — |
createMemoryNotificationAdapter(items, { latency, pageSize, onAction })In-memory adapter with push(n) for demos and tests. | MemoryNotificationAdapter | — |
groupNotifications(items, timeZone)Day grouping in the provider's time zone. | groups | — |