WebberUI

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.

Loading preview…
npx shadcn@latest add https://webberui.com/r/notification-triage-panel.json

Playground

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.json

Or, 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

PropTypeDefaultDescription
notificationsTriageNotification[]Notification list in controlled mode; pair it with onNotificationsChange
defaultNotificationsTriageNotification[][]Initial notification list in uncontrolled mode
onNotificationsChange(next: TriageNotification[]) => voidCallback 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") => voidCallback for switching tabs
titleReact.ReactNode"通知"Panel title
relativeTimebooleantrueShow relative time; when off, shows HH:MM
localestringLocale for time formatting (BCP 47)
emptyStateReact.ReactNodeWhat to show when the list is empty

TriageNotification

FieldTypeDescription
idstringStable unique identifier; the animation matches before and after states by it
titleReact.ReactNodePrimary title
descriptionReact.ReactNodeSecondary description text
sourcestringSource name; the grouping key when groupBy="source"
iconReact.ReactNodeIcon on the left; a bell by default
timestampDate | number | stringEvent time; the grouping key when groupBy="date"
readbooleanWhether 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 a role="tablist", and the tab buttons carry aria-selected and support to switch.
  • Group header buttons carry aria-expanded and aria-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.

On this page