Bell
The notifications button in the app bar, with a count of what is unread.
SourcePackage @weft-js/warp
Preview
tsx
import { derived, state } from '@weft-js/core'import { Bell, Inbox, Toaster } from '@weft-js/warp'import type { Note } from '@weft-js/warp'export function WithInbox() { const inbox = useInbox() const unread = derived(() => inbox.items.value.filter(n => !n.read).length) return ( <> <Bell count={unread} inbox={open => <Inbox variant="popover" {...inbox} open={open.value} />} /> <Toaster /> </> )}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 { Bell } from '@weft-js/warp'New arrivals
The count bumps once for each batch. Opening clears it here.
tsx
import { state } from '@weft-js/core'import { Bell, Button, Row } from '@weft-js/warp'export function Arrivals() { const count = state(3) return ( <Row gap="related"> <Bell count={count} onOpen={() => (count.value = 0)} /> <Button emphasis="outline" tone="neutral" size="sm" onClick={() => count.value++}>A deploy failed</Button> </Row> )}Accessibility
- Its name says the count. The badge itself is hidden from assistive technology.
API Reference
| Prop | Type | Default | Description |
|---|---|---|---|
count | number | { value: number; } | (() => number | undefined) | — | A number, a state or a getter. |
inbox | ((open: State<boolean>) => unknown) | — | The panel. Gets open, a state. |
route | (() => void) | — | On a phone, called instead of opening. |
side | Side | 'bottom' | Which edge it sits on. |
align | Align | 'end' | How it lines up with what opened it. |
| Event | Type | Default | Description |
|---|---|---|---|
onOpen | (() => void) | — | It was pressed. |
Parts
Every element carries a stable name, so you can style it from your own CSS: [data-scope="bell"] [data-part="content"]
content
RelatedInboxConnection pill