caliper uidocs

Notification center

Block

A list of notifications with read state and filters.

Installs components/ui/notification-center.tsxDepends on @radix-ui/react-popover, @radix-ui/react-slot, lucide-reactMotion none in component source
pnpm dlx shadcn@latest add https://caliperui.site/r/notification-center.json

When to use #

Use it for

  • A bell menu or inbox page.

Skip it when

  • Messages that need an immediate reply.

Installation #

terminal
pnpm dlx shadcn@latest add https://caliperui.site/r/notification-center.json

Adds its 4 files to components/ui/, each with its own stylesheet, and installs their packages. No account needed. The first part also adds Caliper UI’s tokens; import them once as in Installation.

Usage #

This is the code behind the preview above.

example.tsx
import { CheckCircle2, GitPullRequest, MessageSquare, TriangleAlert, UserPlus } from "lucide-react"
import { useState } from "react"
import { NotificationCenter, type NotificationItem } from "@/components/ui/notification-center"

const seed: NotificationItem[] = [
  { id: "n1", group: "Today", title: "Maya commented on “Pricing page”", body: "Can we make the empty state feel like a next step instead of an error?", time: "4 min ago", read: false, icon: <MessageSquare /> },
  { id: "n2", group: "Today", title: "Deploy to production succeeded", body: "Build 2184 finished in 1m 12s.", time: "32 min ago", read: false, icon: <CheckCircle2 /> },
  { id: "n3", group: "Today", title: "Dev requested your review", body: "Add undo to bulk archive (#418)", time: "2 h ago", read: false, icon: <GitPullRequest /> },
  { id: "n4", group: "Earlier", title: "Billing card expires next month", body: "Update your payment method to avoid interruptions.", time: "Yesterday", read: true, icon: <TriangleAlert /> },
  { id: "n5", group: "Earlier", title: "Sam joined the workspace", time: "Mon", read: true, icon: <UserPlus /> },
];

export function NotificationCenterDemo() {
  const [items, setItems] = useState(seed);
  const [, setLog] = useState("");
  return (
    <NotificationCenter
                  notifications={items}
                  onOpen={(id) => {
                    setItems((l) => l.map((n) => (n.id === id ? { ...n, read: true } : n)));
                    setLog(`Opened: ${items.find((n) => n.id === id)?.title}`);
                  }}
                  onRead={(id, read) => setItems((l) => l.map((n) => (n.id === id ? { ...n, read } : n)))}
                  onReadAll={() => setItems((l) => l.map((n) => ({ ...n, read: true })))}
                  onDismiss={(id) => setItems((l) => l.filter((n) => n.id !== id))}
                />
  );
}

API reference #

NotificationCenter

component
PropTypeDefaultDescription
notifications *readonly NotificationItem[]—
onOpen((id: string) => void)—A notification was chosen. It is marked read by the caller.
onRead((id: string, read: boolean) => void)—
onReadAll(() => void)—
onDismiss((id: string) => void)—
labelstring"Notifications"
classNamestring—

Accessibility #

It exposes group and status semantics, with aria-label, aria-hidden and aria-pressed kept in step with state. Changes that matter are announced through a live region.

Keyboard and focus handling comes from Radix popover: arrow keys move through items, Enter and Space select, and Escape closes and returns focus.

Motion #

It declares no animation in its component source. Any transition comes from its stylesheet, which uses the duration tokens that collapse to instant under prefers-reduced-motion.