Notification Triage Panel
A notification center panel grouped by date or source, with unread/all tabs and collapsible groups; on a bulk clear the items sweep out in sequence and the group height springs closed behind them.
npx shadcn@latest add https://webberui.com/r/notification-triage-panel.jsonPlayground
Tune the props live — the code snippet updates as you go, so you can dial in the look you want before copying it.
<NotificationTriagePanel />
Installation
npx shadcn@latest add https://webberui.com/r/notification-triage-panel.jsonOr, once registries are configured in components.json, install it as @webberui/notification-triage-panel.
Usage
import {
NotificationTriagePanel,
type TriageNotification,
} from "@/components/ui/notification-triage-panel";
const notifications: TriageNotification[] = [
{
id: "1",
title: "Aria mentioned you",
description: "Replied to your comment in the thread.",
source: "Slack",
accent: "violet",
timestamp: Date.now() - 4 * 60_000,
},
{
id: "2",
title: "PR #482 is waiting for your review",
source: "GitHub",
accent: "green",
read: true,
timestamp: Date.now() - 42 * 60_000,
},
];
export function Example() {
const [items, setItems] = React.useState(notifications);
return (
<NotificationTriagePanel
notifications={items}
onNotificationsChange={setItems}
groupBy="date"
/>
);
}Without notifications, use defaultNotifications to enter uncontrolled mode, where the component manages the cleared and read states itself.
Props
NotificationTriagePanel
| Prop | Type | Default | Description |
|---|---|---|---|
notifications | TriageNotification[] | — | Notification list in controlled mode; pair it with onNotificationsChange |
defaultNotifications | TriageNotification[] | [] | Initial notification list in uncontrolled mode |
onNotificationsChange | (next: TriageNotification[]) => void | — | Callback for clearing or marking as read |
groupBy | "date" | "source" | "date" | What to group by |
tab | "unread" | "all" | — | Current tab in controlled mode |
defaultTab | "unread" | "all" | "unread" | Initial tab in uncontrolled mode |
onTabChange | (tab: "unread" | "all") => void | — | Callback for switching tabs |
title | React.ReactNode | "通知" | Panel title |
relativeTime | boolean | true | Show relative time; when off, shows HH:MM |
locale | string | — | Locale for time formatting (BCP 47) |
emptyState | React.ReactNode | — | What to show when the list is empty |
TriageNotification
| Field | Type | Description |
|---|---|---|
id | string | Stable unique identifier; the animation matches before and after states by it |
title | React.ReactNode | Primary title |
description | React.ReactNode | Secondary description text |
source | string | Source name; the grouping key when groupBy="source" |
icon | React.ReactNode | Icon on the left; a bell by default |
timestamp | Date | number | string | Event time; the grouping key when groupBy="date" |
read | boolean | Whether it has been read |
accent | "neutral" | "blue" | "green" | "amber" | "red" | "violet" | Accent color of the icon's base |
How the animation works
- Sequenced sweep-out: on a bulk clear (a whole group, or "clear all") the items slide out to the right in display order with a stagger; only after they have left does the height spring closed, and the items below flow up into the gap.
- Group collapse and backfill: once a whole group is empty, the group block itself springs its height closed and the groups below rise naturally to fill in. Clicking a group header collapses or expands it, with the content height using the same spring transition.
- One at a time: clicking an item marks it as read (in the unread tab it fades out and collapses); on hover or focus a clear button appears on the right, and clearing a single item sweeps out the same way.
- Tuning points: both the collapse spring and the sweep duration can be influenced through tokens such as
var(--wb-duration-fast,200ms), which also drive the transition colors.
Accessibility
- The panel is a
role="region", the tab strip arole="tablist", and the tab buttons carryaria-selectedand support←→to switch. - Group header buttons carry
aria-expandedandaria-controls, so collapsing works from the keyboard. - The body of each notification is a button (mark as read) and the clear button is separate with its own
aria-label, avoiding nested interactive elements. - When the user has "reduce motion" enabled at the system level, the sweep offsets and springs are replaced by a brief fade in / fade out, with no effect on how it is operated.
Setup Checklist Rail
An onboarding checklist pinned to the side of the app — completed items check off and collapse, the progress ring at the top counts up, and once everything is done it shrinks into an expandable floating badge.
Miller Column Browser
A Finder-style Miller-column hierarchy browser: every level you descend springs a new column in from the right, the whole row pans smoothly once it overflows, and breadcrumbs highlight the current path in step.