Notification Center
A notification inbox behind a bell: a rolling unread badge, a bell that swings on arrival, All / Mentions / Unread tabs with a sliding indicator, Today / Earlier groups, swipe-to-archive with FLIP collapse and designed empty states.
DOM + CSS0 deps1 file · 37 KB TSXreduced-motion aware
Props
"use client";
import { useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
import type { CSSProperties, PointerEvent as ReactPointerEvent, KeyboardEvent as ReactKeyboardEvent } from "react";
export type NotificationItem = {
id: string;
/** Who did it. Drives the generated initials avatar and its colour. */
actor: string;
/** Verb phrase, e.g. "requested your review on". */
action: string;
/** The thing acted on, e.g. "Pricing page v3". */
target: string;
/** Minutes since it happened, measured at mount. Drives order, grouping and the relative time. */
ago: number;
/** Defaults to true. */
unread?: boolean;
/** Shows in the Mentions tab and gets a small chip. */
// ---------------------------------------------------------------
// Full source available with a viberdy Pro subscription.
// https://viberdy.dev/pricing
// ---------------------------------------------------------------
}Details
About this component
A bell trigger opens an inbox panel that behaves the way the best product inboxes do. The unread badge is an odometer: it rolls through the digits when the count changes, and the bell swings with a soft ring when something new arrives. Inside, a segmented control (All, Mentions, Unread) slides a pill between tabs, items are grouped under Today and Earlier, and every row carries a generated-initials avatar whose colour is derived from the name, so the same person always looks the same. Rows read as actor, action and target with a relative time. Hovering or focusing a row fades in mark-read and archive buttons; on touch you swipe left to archive, with a rubber-band past the threshold and a spring back if you let go early. Archiving slides the row away and the rest of the list glides up with a FLIP animation, and new arrivals ease in at the top. Mark all as read fades the unread dots in a stagger, and every tab has its own designed empty state. Read and archive state is kept per id on top of the feed you pass in, so it drops straight onto a real data source: append to items to push an arrival, and use onMarkRead and onArchive to persist. The demo prop simulates an arrival on an interval and is off by default. Below 640px the popover becomes a full-width bottom sheet with a backdrop.
Curator’s note
Rebuilt in October as a full inbox: rolling badge, swipe-to-archive with FLIP, grouped feed and designed empty states, with no dependencies.

