Skip to content
weftGitHub

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/warp

Usage

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

PropTypeDefaultDescription
countnumber | { 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.
sideSide'bottom'Which edge it sits on.
alignAlign'end'How it lines up with what opened it.
EventTypeDefaultDescription
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