Component reference
NotificationCenter
A controlled inbox with read and dismiss actions.
Preview
Live previewOptional six-digit hex. Clear to restore the preset. Check contrast with your brand color.
Changes apply to this preview only.
Acme workspace
Notifications stay local to this demo; the component has no global store.
Width adapts to the available space
"use client";
import {
Card,
CardHeader,
CardTitle,
CardDescription,
Badge,
type PimoluNotification,
NotificationCenter,
CardContent,
Button
} from "@pimolu/react";
import { useState } from "react";
function MentionIcon() {
return (
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
aria-hidden="true"
>
<circle cx="12" cy="8" r="3.5" />
<path d="M5 20a7 7 0 0 1 14 0" />
</svg>
);
}
function BillingIcon() {
return (
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
aria-hidden="true"
>
<rect x="3" y="5" width="18" height="14" rx="2" />
<path d="M3 10h18" />
</svg>
);
}
function SystemIcon() {
return (
<svg
viewBox="0 0 24 24"
width="15"
height="15"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
>
<path d="M12 3v3" />
<path d="m5.6 5.6 2.1 2.1" />
<path d="M3 12h3" />
<path d="M12 21a6 6 0 0 0 6-6c0-3-6-9-6-9S6 12 6 15a6 6 0 0 0 6 6Z" />
</svg>
);
}
const initialNotifications: PimoluNotification[] = [
{
id: "mention",
title: "Ada mentioned you in Q3 report",
description: "“Can you review the revenue section before Friday?”",
time: "8 min ago",
read: false,
icon: <MentionIcon />,
},
{
id: "billing",
title: "Payment received",
description: "Invoice #1042 for $4,800 was paid by Acme GmbH.",
time: "42 min ago",
read: false,
icon: <BillingIcon />,
},
{
id: "system",
title: "Scheduled maintenance",
description: "The reporting API will be read-only on Sunday from 02:00 to 04:00 UTC.",
time: "Yesterday",
read: true,
icon: <SystemIcon />,
},
{
id: "digest",
title: "Your weekly digest is ready",
description: "12 projects changed, 4 mentions and 2 reviews are waiting.",
time: "Monday",
read: true,
icon: <SystemIcon />,
},
];
export function DemoExample() {
const [outcome, setOutcome] = useState("No notification action yet");
const [panelOpen, setPanelOpen] = useState(false);
const [notifications, setNotifications] = useState<PimoluNotification[]>(initialNotifications);
function markRead(id: string) {
setNotifications((current) => current.map((notification) => notification.id === id
? { ...notification, read: true }
: notification));
setOutcome(`Marked ${id} as read`);
}
function markAllRead() {
setNotifications((current) => current.map((notification) => ({
...notification,
read: true,
})));
setOutcome("Marked every notification as read");
}
function dismiss(id: string) {
setNotifications((current) => current.filter((notification) => notification.id !== id));
setOutcome(`Dismissed ${id}`);
}
const notificationsWithActions: PimoluNotification[] = notifications.map((notification) => {
if(notification.id === "mention") {
return {
...notification,
action: {
label: "Open report",
onClick: () => {
markRead("mention");
setOutcome("Opened the Q3 report");
},
},
};
}
if(notification.id === "billing") {
return {
...notification,
action: {
label: "View invoice",
onClick: () => {
markRead("billing");
setOutcome("Opened invoice #1042");
},
},
};
}
return notification;
});
const unreadCount = notifications.filter((notification) => !notification.read).length;
return (
<Card>
<CardHeader>
<div
className="flex flex-wrap items-center gap-3 min-w-0 justify-between"
style={{
flexWrap: "wrap"
}}
>
<div>
<CardTitle>Acme workspace</CardTitle>
<CardDescription>
Notifications stay local to this demo; the component has no
global store.
</CardDescription>
</div>
<div className="flex flex-wrap items-center gap-3 min-w-0">
<Badge variant="primary">
{unreadCount} unread
</Badge>
<NotificationCenter
notifications={notificationsWithActions}
onRead={markRead}
onReadAll={markAllRead}
onDismiss={dismiss}
showTabs
open={panelOpen}
onOpenChange={setPanelOpen}
/>
</div>
</div>
</CardHeader>
<CardContent>
<div className="grid gap-5 min-w-0">
<div
className="flex flex-wrap items-center gap-3 min-w-0 justify-between"
>
<span>Last action</span>
<span className="text-sm text-muted leading-relaxed">{outcome}</span>
</div>
<div className="flex flex-wrap items-center gap-3 min-w-0">
<Button
size="sm"
appearance="outline"
onClick={() => setPanelOpen(true)}
>
Open from the page
</Button>
<Button
size="sm"
appearance="ghost"
onClick={() => {
setNotifications(initialNotifications);
setOutcome("Demo notifications reset");
}}
>
Reset notifications
</Button>
</div>
</div>
</CardContent>
</Card>
);
}More examples
Different configurations and compositions using the real Pimolu API. Each preview runs independently and follows the site appearance.
Read and dismiss
The application owns read state and dismissal; the count and list reflect those changes.
Empty inbox
A calm empty message, with no invented notification count or action.
Without tabs
Tabs are opt-in. The same panel works with a single list, a custom empty label and a placed action button.
Usage
Keep notification state in the application; callbacks report intent rather than persisting data.
API reference
Generated from the public TypeScript declarations. Native HTML attributes are inherited where the component extends them; this reference includes Pimolu props and selected common native attributes.
PimoluNotificationAction
Type source| Name | Type | Default | Description |
|---|---|---|---|
labelRequired | ReactNode | — | Visible label associated with the control. |
onClickRequired | () => void | — | Public Pimolu prop; see the source declaration for details. |
labelRequiredReactNodeNo default extracted.
Visible label associated with the control.
onClickRequired() => voidNo default extracted.
Public Pimolu prop; see the source declaration for details.
PimoluNotification
Type source| Name | Type | Default | Description |
|---|---|---|---|
idRequired | string | — | Public Pimolu prop; see the source declaration for details. |
titleRequired | ReactNode | "Notifications" | Public Pimolu prop; see the source declaration for details. |
description | ReactNode | — | Public Pimolu prop; see the source declaration for details. |
time | ReactNode | — | Human-readable time, rendered as-is (for example "12 min ago"). |
read | boolean | undefined | — | Unread when omitted. |
icon | ReactNode | — | Optional leading glyph. It is decorative. |
action | PimoluNotificationAction | undefined | — | Optional inline action, such as "View invoice". |
idRequiredstringNo default extracted.
Public Pimolu prop; see the source declaration for details.
titleRequiredReactNodeDefault: "Notifications"
Public Pimolu prop; see the source declaration for details.
descriptionReactNodeNo default extracted.
Public Pimolu prop; see the source declaration for details.
timeReactNodeNo default extracted.
Human-readable time, rendered as-is (for example "12 min ago").
readboolean | undefinedNo default extracted.
Unread when omitted.
iconReactNodeNo default extracted.
Optional leading glyph. It is decorative.
actionPimoluNotificationAction | undefinedNo default extracted.
Optional inline action, such as "View invoice".
PimoluNotificationCenterProps
Type source| Name | Type | Default | Description |
|---|---|---|---|
notificationsRequired | PimoluNotification[] | — | Public Pimolu prop; see the source declaration for details. |
onRead | ((id: string) => void) | undefined | — | Marks one notification as read. |
onReadAll | (() => void) | undefined | — | Marks every notification as read. |
onDismiss | ((id: string) => void) | undefined | — | Removes one notification. Hides the dismiss button when omitted. |
open | boolean | undefined | — | Controlled open state. Omit to let the component own it. |
defaultOpen | boolean | undefined | false | Initial open state when uncontrolled. Defaults to false. |
onOpenChange | ((open: boolean) => void) | undefined | — | Called whenever the panel asks to open or close. |
placement | PimoluPopoverPlacement | undefined | "bottom" | Preferred side of the trigger. Defaults to "bottom". |
showTabs | boolean | undefined | false | Show the All / Unread filter tabs. Defaults to false. |
defaultTab | PimoluNotificationTab | undefined | "all" | Initial tab when tabs are enabled. Defaults to "all". |
triggerLabel | string | undefined | "Notifications" | Accessible name of the bell. Defaults to "Notifications". |
title | string | undefined | "Notifications" | Heading of the panel. Defaults to "Notifications". |
readAllLabel | string | undefined | "Mark all as read" | Label of the mark-all action. Defaults to "Mark all as read". |
tabsLabel | string | undefined | "Notification filters" | Accessible name of the tab list. Defaults to "Notification filters". |
dismissLabel | string | undefined | "Dismiss notification" | Accessible name of a dismiss button. Defaults to "Dismiss notification". |
emptyLabel | ReactNode | — | Replaces the default empty message. |
emptyState | ReactNode | — | Replaces the whole empty state. |
className | string | undefined | — | Class applied to the popover panel. |
notificationsRequiredPimoluNotification[]No default extracted.
Public Pimolu prop; see the source declaration for details.
onRead((id: string) => void) | undefinedNo default extracted.
Marks one notification as read.
onReadAll(() => void) | undefinedNo default extracted.
Marks every notification as read.
onDismiss((id: string) => void) | undefinedNo default extracted.
Removes one notification. Hides the dismiss button when omitted.
openboolean | undefinedNo default extracted.
Controlled open state. Omit to let the component own it.
defaultOpenboolean | undefinedDefault: false
Initial open state when uncontrolled. Defaults to false.
onOpenChange((open: boolean) => void) | undefinedNo default extracted.
Called whenever the panel asks to open or close.
placementPimoluPopoverPlacement | undefinedDefault: "bottom"
Preferred side of the trigger. Defaults to "bottom".
showTabsboolean | undefinedDefault: false
Show the All / Unread filter tabs. Defaults to false.
defaultTabPimoluNotificationTab | undefinedDefault: "all"
Initial tab when tabs are enabled. Defaults to "all".
triggerLabelstring | undefinedDefault: "Notifications"
Accessible name of the bell. Defaults to "Notifications".
titlestring | undefinedDefault: "Notifications"
Heading of the panel. Defaults to "Notifications".
readAllLabelstring | undefinedDefault: "Mark all as read"
Label of the mark-all action. Defaults to "Mark all as read".
tabsLabelstring | undefinedDefault: "Notification filters"
Accessible name of the tab list. Defaults to "Notification filters".
dismissLabelstring | undefinedDefault: "Dismiss notification"
Accessible name of a dismiss button. Defaults to "Dismiss notification".
emptyLabelReactNodeNo default extracted.
Replaces the default empty message.
emptyStateReactNodeNo default extracted.
Replaces the whole empty state.
classNamestring | undefinedNo default extracted.
Class applied to the popover panel.
Accessibility
- Name the bell and panel, verify tabs and item actions, and keep dismiss actions separate from reading.