Skip to content
weftGitHub

Inbox

A person's notifications, by day, with tabs, actions and undo.

SourcePackage @weft-js/warp

Preview

tsx
import { state } from '@weft-js/core'import { Inbox, Toaster } from '@weft-js/warp'import type { Note } from '@weft-js/warp'export function Page() {  const inbox = useInbox()  return (    <div style="block-size:520px">      <Inbox title="Notifications" live="live" {...inbox} />      <Toaster />    </div>  )}const NOTES: Note[] = [  { id: 'n1', actor: 'Sam Okafor', kind: 'mention', verb: 'mentioned you in', object: 'mobile-bff', time: '9 min', day: 'Today', source: 'Deploys', quote: 'Can you look at the retry settings before we redeploy?' },  { id: 'n2', actor: 'Jun Tanaka', kind: 'approve', verb: 'asked you to approve', object: 'Release 2026.11', time: '32 min', day: 'Today', actions: [{ id: 'approve', label: 'Approve' }, { id: 'view', label: 'View', emphasis: 'outline' }] },  { id: 'n3', actor: 'weft', kind: 'system', verb: 'failed the health check on', object: 'd-4819', detail: 'in ap-south', time: '1 h', day: 'Today' },  { id: 'n4', actor: 'Mara Lindqvist', kind: 'invite', verb: 'invited you to', object: 'Incident review', detail: '· 13:00', time: '2 h', day: 'Today', source: 'Calendar', read: true },  { id: 'n5', actor: 'Priya Raman', kind: 'comment', verb: 'commented on', object: 'storefront', time: 'Yesterday', day: 'Yesterday', quote: 'The edge cache cut image load times in half.', read: true },  { id: 'n6', actor: 'Lena Fischer', kind: 'assign', verb: 'assigned you', object: 'Ranking v2 handover', time: 'Yesterday', day: 'Yesterday', read: true },]const min = 60_000function useInbox() {  const items = state(NOTES)  const change = (id: string, patch: Partial<Note>) => (items.value = items.value.map(n => (n.id === id ? { ...n, ...patch } : n)))  return {    items,    onRead: (id: string) => change(id, { read: true }),    onReadAll: () => (items.value = items.value.map(n => (n.read ? n : { ...n, read: true }))),    onDismiss: (id: string) => (items.value = items.value.filter(n => n.id !== id)),    onRestore: (note: Note) => (items.value = [note, ...items.value]),    onAction: (note: Note, action: string) => change(note.id, { read: true, actions: undefined, detail: action === 'approve' ? '· approved' : note.detail }),  }}

Installation

npm install @weft-js/warp

Usage

tsx
import { Inbox } from '@weft-js/warp'

First load

items stays undefined until it arrives.

tsx
import { Inbox } from '@weft-js/warp'export function Loading() {  return (    <Inbox items={undefined} />  )}

Empty

An Inbox with no items shows its empty state.

tsx
import { Inbox } from '@weft-js/warp'import type { Note } from '@weft-js/warp'export function Empty() {  return (    <Inbox items={NOTHING} />  )}const NOTHING: Note[] = []

When to use

Use it for

  • Things that need someone's attention

Reach for something else

Accessibility

LeftandRight
Move between the tabs
UpandDown
Move between notifications
  • Rows are a feed, keyed, so a change patches one row and focus stays where it was.
  • It needs a mounted Toaster for Undo.

API Reference

PropTypeDefaultDescription
itemsNote[] | { value: Note[] | undefined; } | (() => Note[] | undefined)—Newest first. Undefined while loading.
liveLiveState | (() => LiveState)—The connection's state, shown in the foot as "Up to date" or "Connecting…".
tabsInboxTab[]['all', 'unread', 'mentions']The filter tabs, in order. Default all, unread and mentions.
titlestring'Notifications'The heading.
variant"popover"—popover for inside a Bell.
openbooleantrueIs it on screen? Rows are only marked read while it is.
queuednumber0Updates waiting to sync.
viewAllstring—A link to the full page.
settings(() => void)—Adds a settings button to the header, and is called when it is pressed.
EventTypeDefaultDescription
onRead((id: string) => void)—Called with a row's id once it has been seen or opened.
onReadAll(() => void)—Called when Mark all as read is pressed.
onDismiss((id: string) => void)—Called with a row's id when it is dismissed.
onRestore((note: Note) => void)—Undo a dismiss: put the row back.
onAction((note: Note, action: string) => void)—A row's button was pressed.
onOpen((note: Note) => void)—Makes each row a link.

Parts

Every element carries a stable name, so you can style it from your own CSS: [data-scope="notification"] [data-part="root"]

rootheaderlisttriggerindicatornew-pillpanelgrouptitledescriptionfooter

RelatedBellActivity feedNotification preferences