Figma 全局通知中心与消息收件箱(Notification Center & Activity Inbox Architecture)组件架构实战:从铃铛红点徽标联动(Unread Badge)、多分类选项卡切换、消息卡片类型拓扑与审批流 CTA 到 Variables 状态机与 Dev Mode 前端 Radix UI / Novu 代码交付
一、引言与架构全景:为什么现代 SaaS 必须建立体系化通知中心? #
在现代 B2B SaaS 平台、企业协同办公套件(如 Slack、飞书、Notion、Linear)以及复杂管理控制台中,全局通知中心(Notification Center / Activity Inbox) 是联结系统事件、协同互动与用户待办任务的核心信息分发枢纽。无论是设计稿被团队成员添加批注、代码合并请求等待审核、工单审批流流转,还是重要系统告警,用户都依赖右上角的铃铛入口实时捕获动态并做出响应。
然而,在大量设计团队的原型与组件库中,通知中心往往存在以下几大通病:
- 轻提示与持久化消息混淆:未能清晰划分即时消失的临时浮层(Toast / Snackbar)与需要持久保留的收件箱(Inbox)。研发盲目套用弹窗或轻提示,导致用户刷新页面后重要待办消息彻底丢失;
- 卡片形态单一无差别:将所有消息简化为“头像 + 一行正文”,无法应对带有复杂操作诉求的业务场景(例如需要直接在通知内点击“同意”或“驳回”的审批卡片,或者包含代码差异与上下文引用的批注卡片);
- 未读状态机与角标脱节:导航栏的未读红点(Unread Badge)、弹窗内部未读行高亮、单条标记已读与“全部标为已读”缺乏统一的状态机变量联动,导致原型交互缺乏说服力;
- 前端协议与无障碍缺失:未对齐现代消息中间件协议(如 Novu、Knock),缺乏屏幕朗读器(
aria-live="polite")焦点流转规范,增加前端二次重构成本。
为了彻底规范通知中心的设计与研发交付,我们梳理出如下四层架构拓扑:
+------------------------------------------------------------------------+
| Figma 全局通知中心(Notification Center Architecture)四层拓扑结构 |
+------------------------------------------------------------------------+
| 1. 导航栏触发器与角标联动层 (Navigation Trigger & Badge Layer) |
| - 铃铛图标容器 (Bell Icon: Default / Active / Hover / Muted) |
| - 绝对定位红点角标 (Absolute Badge: Unread Dot / 1~99 Count / 99+) |
+------------------------------------------------------------------------+
| 2. 浮动收件箱面板容器层 (Inbox Popover Canvas & Viewport Layer) |
| - 三段式布局结构 (Header Title, Tabs Filter Bar, Footer View-All) |
| - 视口尺寸约束 (Width: 380px, Max-H: 520px, Auto Layout Hug/Scroll) |
| - 业务缺省容错态 (Empty State 插画与文字引导, 骨架屏 Skeleton) |
+------------------------------------------------------------------------+
| 3. 原子消息卡片变体矩阵层 (Atomic Notification Card Component Set) |
| - 业务类型多态 (System Announcement, Comment Mention, Task Approval)|
| - 阅读状态区分 (Unread 蓝点高亮态 vs Read 浅灰次级态) |
| - 悬停浮动快捷手柄 (Hover Quick Action: Mark as Read, Delete) |
+------------------------------------------------------------------------+
| 4. 语义 Tokens 与状态机层 (Semantic Tokens & Variables State Machine) |
| - 浅色/暗黑主题 Tokens (Surface Popover, Card Unread Tint, Border) |
| - 动态数值与布尔变量绑定 (unreadCount, activeTab, isPopoverOpen) |
+------------------------------------------------------------------------+
二、导航栏触发器与未读红点徽标(Trigger & Badge)联动设计 #
通知中心的入口通常位于全局导航栏(Top Navigation)右上角。该组件由基础操作图标与绝对定位的角标图层组合而成。
1. 触发器容器与绝对定位角标配置 #
在 Figma 中配置铃铛触发器:
- 底座图标按钮(Icon Button):
- 尺寸:
36px × 36px(内边距8px),内部放置20px矢量铃铛图标(Bell Icon); - 悬停(Hover)背景填充浅灰圆角底色(
R: 8px); - 激活(Active/Opened)变体可设为品牌浅蓝底色,图标呈实心深色态;
- 尺寸:
- 未读角标(Unread Badge)绝对定位法则:
- 角标图层开启 Absolute position(绝对定位),锚定在右上角;
- 偏移量设定:
Top: 2px, Right: 2px,避免遮挡铃铛主体轮廓; - 角标形态提供三种变体:
Dot:8px × 8px纯红实心圆点,用于弱感知场景;Count:高度16px,最小宽度16px,左右内边距PX: 4px,圆角999px(Pill 胶囊形),显示数值1~99;Overflow:固定显示99+,宽度自动延伸。
- 高对比防粘连描边:
- 角标外层必须施加一条
1.5px的外描边,颜色与导航栏背景色完全一致(例如白色或深灰色),确保红点在复杂背景上清晰可辨。角标结构可对照学习 Figma 徽标、标签与气泡角标组件架构实战。
- 角标外层必须施加一条
+-------------------------------------------------------------------------+
| Bell Trigger 与 Absolute Position 角标推导 |
+-------------------------------------------------------------------------+
| +-------------------------+ |
| | Top Nav Bar | |
| | | |
| | [ ( 🔔 ) ] (Top: 2px, Right: 2px) |
| | \ | |
| | +--- Absolute Badge: ( 3 ) Red Pill |
| +-------------------------------------------------------------------+
三、通知中心浮动面板(Inbox Popover)三段式架构 #
当用户点击铃铛时,通知面板以浮动覆盖层(Popover Overlay)形式展开。其尺寸通常推荐固定宽度 380px,最大高度 520px。
1. 三段式结构剖析 #
- 顶部操作栏(Header Bar):
- 高度
48px,水平内边距PX: 16px; - 左侧为主标题“通知”(Heading 3,16px SemiBold)与未读总数灰色胶囊标牌(如
(3)); - 右侧包含“全部标为已读(Mark all read)”文本按钮与消息设置(Settings ⚙️)图标;
- 高度
- 选项卡过滤导轨(Tabs Filter Bar):
- 采用紧凑型分段控制器或下划线 Tabs:
全部(All)、未读(Unread)、提及我的(@Mentions)、待办审批(Tasks); - 切换选项卡时,下方列表进行条件过滤。标签页设计细节可参考 Figma 标签页与分段控制器架构实战;
- 采用紧凑型分段控制器或下划线 Tabs:
- 消息滚动视口(Scroll Viewport):
- Auto Layout 纵向排列,宽度设为 Fill container;
- 开启原型纵向滚动(Vertical Scrolling),保证内容超出时顶部与底部操作栏吸顶/吸底固定;
- 底部导流栏(Footer Bar):
- 高度
40px,居中放置“查看全部历史通知”超链接,点击可跳转至全屏独立消息管理大厅。
- 高度
2. 缺省空状态(Empty State)架构 #
当当前选项卡下没有消息时,列表区域动态展示空状态插槽:
- 包含
80px浅灰矢量收件箱插画; - 文案分级:主文案“太棒了,所有消息都已处理完”,副文案“新动态将在此第一时间通知您”;
- 保持整体高度与最小阅读舒适度平衡,具体布局规范可参考 Figma 空状态与结果反馈系统组件架构实战。
四、原子消息卡片(Notification Card)四维变体矩阵设计 #
通知卡片(Notification Card)是承载单条事件的最小完整交互单元。在 Figma 中建立名为 _Notification / Card-Item 的主组件,具备如下变体维度:
1. 四类典型业务卡片形态对照表 #
| 卡片类型(Type) | 视觉核心构成 | 典型业务场景与内容 | 快捷操作(Actions) |
|---|---|---|---|
| 基础系统公告 (System) | 品牌/系统图标(24px),主次双行标题 | 平台版本发布、安全防护预警、扣费通知 | “查看详情”外部链接 |
| 协同评论提及 (Mention) | 成员头像(32px)+ 动作说明 + 引用框 | “Alex 在设计系统规范中提及了你” | “一键回复”浮动输入框 |
| 待办审批流 (Approval) | 申请人头像 + 工单单号 + 审批摘要 | “财务报销工单审批”、“项目上线发布审批” | “同意(Approve)”、“驳回(Reject)”双 CTA |
| 任务风险预警 (Warning) | 橙黄/红色三角感叹号 + 剩余到期倒计时 | “SSL 证书将于 3 天后过期”、“集群负载过高” | “立即处理”警示按钮 |
2. 阅读状态视觉编码规则 #
- 未读态(Unread):
- 卡片左上角绝对定位一个
6px × 6px的品牌蓝高亮实心圆点; - 卡片背景填充带有极浅品牌色微调的浅蓝底色(
rgba(59, 130, 246, 0.04)); - 主标题文本为加粗深色(
Text/Primary);
- 卡片左上角绝对定位一个
- 已读态(Read):
- 隐藏未读蓝点;
- 背景恢复为纯白或面板底色(透明背景);
- 标题降级为次级灰度文本(
Text/Secondary);
- 悬停快捷手柄(Hover Actions):
- 当鼠标 Hover 在单条卡片上时,卡片右上角的时间戳(如
10m 前)暂时淡出,取而代之浮现“标为已读(勾选图标)”与“删除该通知(垃圾桶图标)”两个微型快捷按钮,实现极速整理。
- 当鼠标 Hover 在单条卡片上时,卡片右上角的时间戳(如
+-------------------------------------------------------------------------+
| 审批流卡片(Actionable Approval)布局结构 |
+-------------------------------------------------------------------------+
| [●] (Avatar) 林思远 提交了《2026 Q4 服务器扩容预算审批》 15m ago |
| 金额: ¥48,500 | 关联项目: 云原生迁移平台 |
| +--------------------------------------------------+ |
| | [ 驳回 (Ghost) ] [ 同意扩容 (Primary CTA) ] | |
| +--------------------------------------------------+ |
+-------------------------------------------------------------------------+
五、Variables 变量绑定与暗黑模式语义 Tokens 体系 #
通知中心涉及多层背景色重叠、边框层次与状态微调,必须通过 Design Tokens 规范 与 Figma Variables 变量 进行系统化驱动。
1. 通知中心专用语义 Tokens 体系表 #
| Token 变量名称 | 浅色模式(Light Mode) | 暗黑模式(Dark Mode) | 作用域与设计语义说明 |
|---|---|---|---|
inbox.surface.popover | #FFFFFF | #1E1E1E | 通知浮层面板主背景色 |
inbox.border.subtle | #E5E7EB | #2E2E2E | 面板轮廓与分割线细边框 |
inbox.card.unread.bg | #F0F7FF | #172338 | 未读消息卡片微蓝浅底色 |
inbox.card.unread.dot | #2563EB | #60A5FA | 未读状态左侧指示圆点 |
inbox.card.hover.bg | #F9FAFB | #262626 | 卡片悬停时的浅灰反馈底色 |
inbox.badge.bg | #EF4444 | #F87171 | 导航栏未读数大红胶囊底色 |
inbox.badge.text | #FFFFFF | #FFFFFF | 未读角标文字纯白色 |
inbox.quote.bg | #F3F4F6 | #2A2A2A | 评论提及引文灰色背景框 |
inbox.quote.border | #D1D5DB | #3D3D3D | 评论引文左侧边框线条 |
2. Variables 变量状态机联动 #
在 Figma 中配置 Component/Notification 集合:
unreadCount(Number):控制铃铛角标显示的数字,当用户点击“全部标为已读”时,通过交互逻辑将该数值赋值为0;hasUnread(Boolean):与角标图层的显隐绑定(当unreadCount > 0时显现);activeTab(String):驱动当前高亮的分类选项卡(all/unread/mention);isPopoverOpen(Boolean):控制弹出层的打开与关闭。
六、Dev Mode 现代前端代码交付:从 Figma 到 Radix UI / Novu #
在向工程团队移交设计稿时,利用 Figma Dev Mode 呈现标准的数据契约与组件化代码。
1. 现代化消息数据契约(JSON AST / Novu Schema) #
{
"id": "notif_892314",
"actor": {
"name": "林思远",
"avatar": "https://assets.example.com/avatar/lin.png"
},
"type": "WORKFLOW_APPROVAL",
"title": "提交了审批申请",
"summary": "《2026 Q4 服务器扩容预算审批》",
"status": "UNREAD",
"createdAt": "2026-11-13T09:20:00Z",
"actions": [
{ "label": "驳回", "actionKey": "REJECT", "variant": "secondary" },
{ "label": "同意扩容", "actionKey": "APPROVE", "variant": "primary" }
]
}
2. 生产级 React + Radix UI Popover + Tailwind CSS 交付代码 #
import React, { useState } from 'react';
import * as Popover from '@radix-ui/react-popover';
import { Bell, CheckCheck, Settings } from 'lucide-react';
interface NotificationItem {
id: string;
sender: string;
content: string;
time: string;
isRead: boolean;
type: 'mention' | 'approval' | 'system';
}
export const NotificationCenter: React.FC = () => {
const [unreadCount, setUnreadCount] = useState<number>(3);
const [notifications, setNotifications] = useState<NotificationItem[]>([
{
id: '1',
sender: '林思远',
content: '在《核心设计系统 2.0》中提及了你并请求代码评审',
time: '10m 前',
isRead: false,
type: 'mention',
},
{
id: '2',
sender: '系统通知',
content: '安全审计告警:发现 1 处跨域策略风险需及时确认',
time: '1h 前',
isRead: false,
type: 'system',
},
]);
const handleMarkAllRead = () => {
setNotifications((prev) => prev.map((item) => ({ ...item, isRead: true })));
setUnreadCount(0);
};
return (
<Popover.Root>
<Popover.Trigger asChild>
<button
className="relative flex items-center justify-center w-9 h-9 rounded-lg hover:bg-gray-100 dark:hover:bg-gray-800 transition-colors focus:outline-none focus:ring-2 focus:ring-blue-500"
aria-label={`通知中心,共 ${unreadCount} 条未读消息`}
>
<Bell className="w-5 h-5 text-gray-700 dark:text-gray-300" />
{unreadCount > 0 && (
<span className="absolute top-1 right-1 flex items-center justify-center min-w-[16px] h-4 px-1 text-[10px] font-bold text-white bg-red-500 rounded-full border-2 border-white dark:border-gray-900">
{unreadCount > 99 ? '99+' : unreadCount}
</span>
)}
</button>
</Popover.Trigger>
<Popover.Portal>
<Popover.Content
className="w-[380px] bg-[var(--inbox-surface-popover)] rounded-xl shadow-2xl border border-[var(--inbox-border-subtle)] z-50 overflow-hidden"
sideOffset={8}
align="end"
>
{/* 头部操作区 */}
<div className="flex items-center justify-between px-4 py-3 border-b border-gray-100 dark:border-gray-800">
<div className="flex items-center gap-2">
<span className="font-semibold text-gray-900 dark:text-gray-100 text-sm">通知</span>
{unreadCount > 0 && (
<span className="px-1.5 py-0.5 text-xs bg-blue-100 text-blue-600 dark:bg-blue-900 dark:text-blue-300 rounded font-medium">
{unreadCount} 条新动态
</span>
)}
</div>
<div className="flex items-center gap-3">
<button
onClick={handleMarkAllRead}
className="flex items-center gap-1 text-xs text-gray-500 hover:text-blue-600 dark:hover:text-blue-400"
>
<CheckCheck className="w-3.5 h-3.5" />
全部已读
</button>
<button className="text-gray-400 hover:text-gray-600 dark:hover:text-gray-200">
<Settings className="w-4 h-4" />
</button>
</div>
</div>
{/* 消息滚动列表 */}
<div className="max-h-[420px] overflow-y-auto divide-y divide-gray-100 dark:divide-gray-800">
{notifications.map((item) => (
<div
key={item.id}
className={`p-3.5 transition-colors hover:bg-[var(--inbox-card-hover-bg)] ${
!item.isRead ? 'bg-[var(--inbox-card-unread-bg)]' : ''
}`}
>
<div className="flex items-start gap-2.5">
{!item.isRead && (
<span className="w-2 h-2 mt-1.5 rounded-full bg-[var(--inbox-card-unread-dot)] shrink-0" />
)}
<div className="flex-1 min-w-0">
<div className="flex items-center justify-between text-xs mb-1">
<span className="font-medium text-gray-900 dark:text-gray-100">{item.sender}</span>
<span className="text-gray-400">{item.time}</span>
</div>
<p className="text-xs text-gray-600 dark:text-gray-300 line-clamp-2 leading-relaxed">
{item.content}
</p>
</div>
</div>
</div>
))}
</div>
</Popover.Content>
</Popover.Portal>
</Popover.Root>
);
};
3. W3C WCAG 2.2 辅助功能(a11y)标准 #
- 实时播报区域:未读消息卡片流声明
role="region"与aria-live="polite",使得新增通知到达时不打断用户当前屏幕阅读; - 键盘焦点流动:ESC 键可立即关闭通知面板并将焦点重置回铃铛触发按钮;Tab 键可在卡片内部的动作按钮(同意/驳回)与标为已读图标之间无障碍流转。
七、常见排障方案与避坑实战 #
在通知中心组件的实际搭建与项目交付中,常发生以下三大高频故障,可对照 Figma 常见报错排障手册 进行快速定位:
- 浮动面板在最右侧边缘被裁切或超出视口:
- 故障现象:当铃铛按钮紧贴屏幕右上角时,宽度为 380px 的面板展开后有 50px 伸出了屏幕右侧边界;
- 根治方案:在 Figma 原型设置中,使用 Open Overlay 并选择对齐方式为
Top Right,或在工程交付中声明 Floating UI 的collisionPadding与flip逻辑,确保浮层右对齐锚定;
- 长标题导致卡片布局被破坏坍塌:
- 故障现象:某条通知的标题极长,未换行直接横向撑开,导致时间戳与右侧删除按钮被挤出卡片;
- 根治方案:必须将正文文本图层的宽度强制设为 Fill container,并在文本排印设置中开启 Auto Height 或在 CSS 交付中标注
-webkit-line-clamp: 2(多行溢出省略);
- 未读角标数字过长被截断:
- 故障现象:当未读数量达到三位数(如 128)时,红点内部文字被吞噬或椭圆变形;
- 根治方案:在 Figma 角标图层中,宽度切勿写死为绝对数字,必须设为 Hug contents,且配置最小宽度(
Min width: 16px)与水平内边距(PX: 4px),或利用组件变体直接拦截为99+。
通过本文详述的触发器红点规范、四类业务卡片矩阵以及现代 Popover 代码架构,团队不仅能在 Figma 中建立结构严密的通知收件箱原型,更能够无缝衔接工程侧的现代通知协议,为产品提供高效可靠的用户触达体验。
核心手册与速查工具
高频手册与实用设计生产力工具直达常见问题与答疑 (FAQ)
高频疑难解析与避坑指南完全不需要。绝大多数核心功能在免费 Starter 计划中均可完整体验与实操练习,无需额外付费购买付费团队版。