ComponentsOverlays & feedback

Notification center

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.

Radix Popover, Sheet, Toast components/notification-center.tsxtheme · material · accent · density · font

Installation

$ pnpm add @vamos-ui/react

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

Leave request from Mert Aydın

Nov 24 – Nov 28 · 5 days annual leave · 9 days left after this

People

Unread

Multi-app inbox

Shared across apps: items carry an app badge, tabs pass any filter to your adapter.

API

NotificationCenter

PropTypeDefault
adapter
list, unreadCount, markRead, onAction?, subscribe?.
NotificationAdapter—
tabs
Filter buttons; filter is passed to adapter.list.
{ id, label, filter? }[]All · Unread · Mentions
toastOnNew
Toast when a live item arrives.
boolean | (n) => booleanfalse
pollInterval
ms between unread-count refreshes without subscribe.
number—
size
380 / 440 px panel.
"md" | "lg""md"
settingsHref
“Notification settings” link in the footer.
string—
open / defaultOpen / onOpenChange
Panel state.
boolean—
children
Custom composition of Trigger, Panel, Tabs, List, Footer.
ReactNode—
labels
Override built-in strings (notifications.*).
Partial<NotificationLabels>—

AppNotification

PropTypeDefault
id / title / body
Content.
string / ReactNode—
createdAt / readAt
Relative time via Intl.RelativeTimeFormat; readAt null = unread.
Date | string—
actor / icon / tone
Avatar, or a tinted icon well.
{ name, avatar? } / ReactNode / tone—
href
Click marks read, navigates and closes the panel.
string—
app / category
App badge; tab filtering.
string—
actions
Inline buttons (Approve / Reject).
{ id, label, variant? }[]—
important
Turns the bell badge red while unread.
boolean—

NotificationItem

PropTypeDefault
notification
The item.
AppNotification—
onOpen / onMarkRead
Link click; the ✓ button.
() => void—
onAction
Inline action.
(actionId) => void | Promise—
pendingAction
Spinner on that action.
string—
posinset / setsize
Feed position (aria).
number—

Parts & helpers

PropTypeDefault
NotificationTrigger
Bell + badge (99+); aria-label “Notifications, 3 unread”.
component—
NotificationPanel
Popover ≥768px, end Sheet below; titled “Notifications”.
component—
NotificationTabs · NotificationList · NotificationFooter
Filters, 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—