Marble UI
← Components

Notification Center

Preview: Default

Bell button with unread count, opening a popover list of notifications (GridList: arrow keys, Enter to open).

Import

import { NotificationCenter } from '@marbleui/ui'

Props

<NotificationCenter>

PropTypeDefaultDescription
items *requiredNotificationItem[]—
emptyTextstring"You're all caught up"
onMarkAllRead() => void—
onOpenItem(id: string) => void—

Accessibility

Keyboard interaction, focus management and ARIA semantics come from the underlying primitives: React Aria DialogTrigger · React Aria Dialog · React Aria GridList · React Aria GridListItem. Follow the primitive's docs for the full keyboard map.

Usage notes

Bell button with an unread badge that opens a popover list of notifications. For transient messages use toast; for a full page of activity use ListView.

<NotificationCenter
  items={items}
  onMarkAllRead={markAllRead}
  onOpenItem={(id) => openNotification(id)}
/>
  • items is controlled data: { id, title, time, body?, read?, avatar? }. You update read yourself in onMarkAllRead and onOpenItem.
  • The button’s aria-label includes the unread count, and the badge caps at “9+”. “Mark all read” appears only when there are unread items and onMarkAllRead is set.
  • The list is a grid list: arrow keys move between items and Enter triggers onOpenItem.
  • emptyText customises the empty message (default “You’re all caught up”).
Example source · show
import { useState } from 'react'
import { Avatar } from '../avatar'
import { NotificationCenter, type NotificationItem } from './notification-center'

const initial: NotificationItem[] = [
  { id: '1', title: 'Maya Chen mentioned you', body: '“@you can you review the new pricing table before Friday?”', time: '2m ago', avatar: <Avatar size="sm" name="Maya Chen" /> },
  { id: '2', title: 'Deploy succeeded', body: 'website · production · 41s', time: '18m ago', avatar: <Avatar size="sm" name="CI" shape="square" /> },
  { id: '3', title: 'Charles Webb assigned ACME-142 to you', time: '1h ago', read: true, avatar: <Avatar size="sm" name="Charles Webb" /> },
  { id: '4', title: 'Invoice paid', body: 'INV-1042 · $1,280.00', time: 'Yesterday', read: true, avatar: <Avatar size="sm" name="Billing" shape="square" /> },
]

function Demo() {
  const [items, setItems] = useState(initial)
  return (
    <div className="flex h-96 items-start">
      <NotificationCenter
        items={items}
        onMarkAllRead={() => setItems(items.map((i) => ({ ...i, read: true })))}
        onOpenItem={(id) => setItems(items.map((i) => (i.id === id ? { ...i, read: true } : i)))}
      />
    </div>
  )
}

export const styles = { Default: Demo, Empty: () => <div className="flex h-64 items-start"><NotificationCenter items={[]} /></div> }

Source: packages/ui/src/notification-center/notification-center.tsx

Catalog

Category
feedback
Priority
Core
Primitive
composed
React Aria
GridList + Popover