Components
NotificationsMenu
The bell next to the avatar: an unread badge on the trigger, a dropdown of recent notifications behind it. Standalone, theme-aware inside a Header — and a good neighbor: opening it closes the UserMenu, and vice versa.
In the header
Drop it into the Header’s actions slot, right next to the UserMenu. The bell shows unreadCount as a badge (capped at 99+), the panel lists your NotificationItem rows, and everything closes on outside click, Escape, or an item click. The two menus never stack: opening one closes the other, so each stays its own control instead of taking the other’s place.
9:41
header.tsx
import { AppShell, Header, NotificationsMenu, NotificationItem, UserMenu } from "appshell-react";
import { GitPullRequest, MessageSquare, Rocket } from "lucide-react";
<Header
behavior="sticky"
logo={<span className="font-bold">Console</span>}
actions={
<>
<NotificationsMenu unreadCount={2}>
<NotificationItem
icon={<Rocket />}
title="Deploy finished"
description="terra-web #482 is live on production."
time="2m"
unread
href="/deploys/482"
/>
<NotificationItem
icon={<MessageSquare />}
title="Mara mentioned you"
time="1h"
unread
onClick={openThread}
/>
<NotificationItem icon={<GitPullRequest />} title="PR #98 merged" time="1d" />
</NotificationsMenu>
<UserMenu username="Mara Kealoha" initials="MK">…</UserMenu>
</>
}
/>API
| Prop | Type | Default | Description |
|---|---|---|---|
| unreadCount | number | 0 | Unread notifications shown as a badge on the bell (caps at 99+). 0 hides the badge. |
| trigger | ReactNode | — | Replace the default bell button content entirely. |
| open | boolean | — | Controlled open state. Omit for uncontrolled. |
| onOpenChange | (open: boolean) => void | — | Open-state change requests. |
| align | "start" | "end" | "end" | Horizontal alignment of the panel relative to the trigger. |
| title | ReactNode | "Notifications" | Panel heading. |
| action | ReactNode | — | Rendered at the right of the heading — a "Mark all read" button. Clicks here keep the menu open. |
| footer | ReactNode | — | Pinned row under the list — a "View all notifications" link. |
| emptyState | ReactNode | — | Shown instead of the built-in empty state when there are no items. |
| children | ReactNode | — | The list — NotificationItem elements or anything else. Clicks on role="menuitem" elements close the menu. |
| className | string | — | Extra classes for the dropdown panel. |
| triggerClassName | string | — | Extra classes for the default trigger button. |
| aria-label | string | — | Accessible name override for the trigger. Defaults to "Notifications", or the "Notifications ({count} unread)" label when unreadCount is greater than 0. |
| Prop | Type | Default | Description |
|---|---|---|---|
| title* | string | — | Main line. |
| description | string | — | Secondary line — clamps to two lines. |
| icon | ReactNode | — | Leading visual — a 16px icon or a small Avatar. |
| time | string | — | Short timestamp — "2m", "yesterday". |
| unread | boolean | false | Marks the row unread: bolder title plus a dot. |
| href | string | — | Renders the row as a link (through the LinkProvider component) instead of a button. |
| onClick | () => void | — | Action handler. |
| className | string | — | Extra classes. |