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/warpUsage
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
- ActivityFeed, when it only records what happened
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
| Prop | Type | Default | Description |
|---|---|---|---|
items | Note[] | { value: Note[] | undefined; } | (() => Note[] | undefined) | — | Newest first. Undefined while loading. |
live | LiveState | (() => LiveState) | — | The connection's state, shown in the foot as "Up to date" or "Connecting…". |
tabs | InboxTab[] | ['all', 'unread', 'mentions'] | The filter tabs, in order. Default all, unread and mentions. |
title | string | 'Notifications' | The heading. |
variant | "popover" | — | popover for inside a Bell. |
open | boolean | true | Is it on screen? Rows are only marked read while it is. |
queued | number | 0 | Updates waiting to sync. |
viewAll | string | — | A link to the full page. |
settings | (() => void) | — | Adds a settings button to the header, and is called when it is pressed. |
| Event | Type | Default | Description |
|---|---|---|---|
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