用途#
告诉用户"我在哪、能去哪"。这套语言里导航的共同点:贴边、窄、当前项用填充或边条表示。
侧轨#
桌面端的主导航。官网首页的做法是一条只放图标的窄轨(实测);云游戏页桌面上用的是一条顶栏,见下面的 顶栏。
结构#
自上而下:标志 → 导航项 → 工具组 → 主行动块 → 次要入口。
规格#
| 项 | 官网(rem / 1920 稿) | 建议值 |
|---|---|---|
| 轨宽 | 7.5rem / 90px | 64 – 72px |
| 导航项高 | 4.5rem / 54px | 44 – 48px |
| 图标 | 宽 2.5 – 2.75rem / 30 – 33px | 20 – 24px |
| 展开宽度 | 19.5rem / 234px | 200 – 240px |
| 展开后的文字 | Medium 1.125rem | text-sm font-medium |
| 底 | #FFFFFF |
surface |
| 阴影 | 0 0 1rem 黑 10% |
shadow-rail |
| 主行动块 | 4.5 × 9.75rem | 宽同导航项,高约 2 倍 |
状态#
| 状态 | 图标 | 底 | 其他 |
|---|---|---|---|
| 默认 | #D9D9D9 |
透明 | — |
| 悬停 | #858585 |
一块 #E5E5E5 的底向右展开 |
文字出现 |
| 当前 | #191919 |
一块 #E6E6E6 的底,比这一项略高 |
底块左缘一条 0.75rem 的墨色竖条;换栏目时整块滑过去 |
主行动块:默认黑底、白色图标与竖排文字;悬停时黄色底层淡入,文字与图标变墨色。
云游戏页"更多"视图左边的卖点目录,当前项和官网首页导航轨的当前项(上表"当前"那一行)是同一个做法:一块 #E6E6E6 的底块,左缘一根 8 设计 px 的 #191919 竖条,换项时整块上下滑(0.16s 匀速);另有一根 148 × 6 设计 px 的品牌黄 #FFF100 下划线从左往右长出来(0.24s)——实测(云游戏页),见 云游戏页。("更多"按 2560px 宽的设计稿整体缩放,1440 宽时是 0.5625 倍。)它只是一列目录(相当于下面的 页内目录),点一项平滑滚到那一节。本库的侧轨照旧不铺底、不加下划线,见下面的 实现。
两种形态#
官网默认只显示图标。这在七个固定栏目的展示站上成立,但对通用场景不够友好,所以组件提供两种形态:
| 形态 | 说明 | 适用 |
|---|---|---|
collapsed |
只显示图标,悬停或聚焦时浮出文字 | 栏目少且固定;屏幕空间紧张 |
expanded |
图标 + 文字常驻,宽 200 – 240px | 默认。 栏目多、用户不熟悉、需要二级导航 |
collapsed 形态下的调整(推断):
- 默认图标色用
ink-secondary而不是#D9D9D9——后者在白底上只有 1.4:1; - 每个项必须有
aria-label或视觉隐藏的文字; - 键盘聚焦时同样展开文字。
展开的底块浮在内容之上,不推挤页面。
实现#
<SideRail aria-label collapsed brand tools action footer>,里面放 SideRailItem 和 SideRailGroup:
<SideRail
aria-label="主导航"
brand={<Logo />}
tools={<IconButton aria-label="切换主题">…</IconButton>}
action={<NavAction href="/console">前往控制台</NavAction>}
>
<SideRailItem icon={<GridIcon />} href="/overview" current>
总览
</SideRailItem>
<SideRailGroup label="资料">
<SideRailItem icon={<ArchiveIcon />} href="/archive">
档案
</SideRailItem>
</SideRailGroup>
</SideRail>| 项 | 取值 |
|---|---|
| 轨宽 | 展开 224px(默认),收起 64px;两者之间的切换是一次宽度过渡 |
| 导航项 | 高 48px;图标 20px,放在一个 24px 的图标位里;文字 text-sm font-medium |
| 默认 | ink-secondary |
| 悬停 | surface-muted 底、ink 字 |
| 当前 | ink、加粗,左缘一条 4 × 20px 的 ink 短竖条;输出 aria-current="page" |
| 禁用 | ink-disabled |
| 底 | surface + shadow-rail + 右缘一条 1px 的 line |
- 当前项没有铺底:官网首页的当前项压在一块浅灰底上,云游戏页"更多"的目录也是(见上面的 状态);这里只有墨色、加粗和左缘的短竖条——
surface-muted的底在这里是悬停。 - 多一条边线:环境阴影是黑色的 10%,在暗色页面上看不出来,侧轨会和页面融在一起。
- 图标不跑位:导航项的左内边距是 20px,加上 24px 的图标位,图标的中线正好在 32px——收起成 64px 时它就在正中,展开收起的过程里图标一动不动。
- 收起时文字怎么浮出来:悬停或键盘聚焦到一项,一块
surface-muted的底从这一项向右展开(裁切过渡,不是淡入),里面是栏目名;这一项同时换成同样的底,两块连成一条。- 这块底挂在
<body>下,不在侧轨里面:侧轨中间那一段在栏目多的时候要能滚动,能滚动的容器会把伸出去的东西裁掉。 - 它只给眼睛看(
aria-hidden,也不挡点击)。栏目名一直在导航项里,收起时是视觉隐藏的文字——读屏读到的名称和展开时一样,不会读两遍。 - 触屏上没有悬停,浮不出来;所以默认是展开,收起只留给栏目少且固定的场合。
- 这块底挂在
- 结构自上而下五段:
brand(标志)→ 导航项 →tools(工具组)→action(主行动块)→footer(次要入口)。视口不够高时,只有导航项那一段滚动,其余四段留在原处。 SideRailItem:icon(必填——收起时只剩它)、current、disabled、end(行尾的计数或角标,收起时不显示);传href是链接,传render交给路由库的链接组件,只传onClick是按钮。SideRailGroup label把几个栏目归成一组:展开时是一行// 小标题的微标,收起时换成一条短线;读屏听到的是一个有名称的子列表。它只是分组,不能收起;能展开收起的见下面的"二级"。- 工具组里放图标按钮:收起时竖排,展开时横排。分享用 展开条,触发按钮是
inverse的图标按钮。 - 整条是一个
<nav>,必须给aria-label。默认sticky、占满视口高、在--z-nav层;要放进一个定高的框里就用className="h-full"盖掉。 - 本库不带标志。
brand传你自己的;收起时只有 64px 宽,自己换成只有图形的那一版。
二级#
一个栏目下面还有几个去处时用。(推断:官网的侧轨只有一级,上面的规格只说了"需要二级导航时用展开形态",这一节的画法是补的。)
| 项 | 取值 |
|---|---|
| 父项 | 和普通栏目同高同样(48px、图标 + 文字),行尾一个 8px 的实心三角;展开时三角从朝右转到朝下 |
| 子项 | 高 40px,没有图标,文字和父项的文字对齐(左起 56px),text-sm |
| 引线 | 子项左侧一条 1px 的 line 竖线,对着父项图标的中线(左起 32px) |
| 子项悬停 | surface-muted 底、ink 字,同一级栏目 |
| 当前子项 | ink、加粗;引线在它那一段换成 3px 的墨色粗条 |
| 展开 | 子项那一段的高度过渡,--duration-base |
- 当前项的短竖条只出现一处。 展开时在子项上(引线上的那段粗条);收着的时候子项看不见,父项替它显示成"当前"的样子——墨色、加粗、左缘的短竖条。
- 当前项在里面时默认展开;之后当前项换到了里面(从别处导航过来),也会自己展开。
- 父项本身不是一个去处:点它只是展开收起。要一个"总览"页,就把它做成第一个子项。
- 只做一层。二级下面还要分,说明这棵树该放进页面里(页签、列表),而不是侧轨。
- 收起的侧轨上没有地方就地展开:父项只剩图标,悬停或点击时向右弹出一块菜单——最上面一行是这一组的名称,下面是子项。面板就是 下拉菜单 的浅色面板,当前子项是
surface-muted底 + 左缘墨色条。
已实现为 <SideRailSub icon label open defaultOpen onOpenChange> + <SideRailSubItem current href render onClick disabled end>:
<SideRailSub icon={<ArchiveIcon />} label="档案">
<SideRailSubItem href="/archive/people" current>
人员
</SideRailSubItem>
<SideRailSubItem href="/archive/stations">站点</SideRailSubItem>
</SideRailSub>- 展开的侧轨上,父项是一个带
aria-expanded的按钮,子项是一个以label命名的子列表;当前子项输出aria-current="page"。父项不输出aria-current——它不是页面。 - 收起的侧轨上是一个真的菜单:回车或空格打开并进到第一项,方向键移动,
Esc关闭并回到父项。悬停也能打开;悬停打开的菜单不锁页面。 - "当前项在不在里面"是从子元素上读的,所以
SideRailSubItem要直接放在SideRailSub里,不要再包一层。 - 子项可以带
end(计数、角标),同一级栏目。
主行动块#
侧轨里的"主行动块"、顶栏里的"主行动"、全屏菜单底部的"通宽深色按钮"是同一个东西,做成了一个控件 <NavAction>:
| 部位 | 取值 |
|---|---|
| 底、字 | surface-inverse、ink-inverse |
| 悬停 | 整块变 action 黄,字变 on-action;按下是 action-pressed |
| 内容 | 12px 的实心三角 → 一条 30% 不透明度的细线 → 文字,font-medium |
| 圆角 | 4px,和按钮一族同一档(官网"前往游戏"块就是 4px,实测) |
| 禁用 | disabled 底、on-disabled 字 |
三种摆法,放在哪个容器里就是哪一种,一般不用自己指定:
layout |
尺寸 | 在哪 |
|---|---|---|
inline |
高 40px,宽随文字 | 顶栏;单独使用时的默认 |
block |
高 48px,通宽 | 展开的侧轨、全屏菜单的底部 |
stacked |
宽 48px,高至少 96px,文字竖排 | 收起的侧轨 |
- 它和
action变体的按钮不是一回事:黄色按钮是"这一屏的主要行动",主行动块是"整个产品最主要的那一个去处",常驻在导航里。一个界面只放一个。 - 暗色主题下
surface-inverse是近白,悬停变黄时明度几乎不变,变的是色相——可以接受,但它在暗色下不如亮色下醒目。 - 传
href是链接,传render交给路由库的链接组件,都不传是按钮。icon换掉默认的三角。 - 云游戏页顶栏右侧的一排导航项是同一个"图标 → 细线 → 文字"的结构:20px 图标、1 × 14px 的竖线、16px 文字,4px 圆角;除"下载"是亮底外,压在黑色顶栏上的都是白 20% 的底,悬停 30%、按下 15%,过渡 0.15s——实测(云游戏页),见 云游戏页。"公告"的图标右上角压一个橙色菱形角标,和 通知角标 是同一个样子。库里的 按钮 取的就是这一排的外形(4px 圆角、图标后一条细竖线、暗色下的半透明白)。主行动块照旧是反转填充、悬停变黄,一个界面只放一个;圆角跟着按钮一族改成了 4px。
顶栏#
移动端和窄屏的主导航(实测)。
| 项 | 官网(竖屏 rem / 1080 稿) | 建议值 |
|---|---|---|
| 高度 | 9.625rem / 154px(约视口宽的 14%) | 56px |
| 底 | #FFFFFF |
surface |
| 阴影 | 0 0 2rem 黑 30% |
shadow-lg |
| 图标钮 | 4.75rem 见方,#F2F2F2 底 |
40px |
| 主行动 | 15.5 × 4.75rem,#191919 底白字 |
高 40px |
内容从左到右:标志 → (弹性空白)→ 工具图标钮 → 主行动 → 菜单钮。
云游戏页在桌面上就用一条顶栏当主导航:通宽 68px,黑 80% 加 5px 背景模糊,不吸顶,跟着页面滚走;自左向右是标志、竖分隔、"首页 / 更多"两个文字页签,右侧一排"图标 + 竖线 + 文字"的导航项——实测(云游戏页),见 云游戏页。这里的 TopBar 仍是 surface 底加阴影、默认吸顶;库里没有毛玻璃。
已实现为 <TopBar brand tools action menu>:
- 高 56px,
surface底 +shadow-lg,另有一条 1px 的底边线(理由同侧轨)。默认sticky贴在顶上,在--z-nav层。 - 它只管排版:四个格子各放什么由使用方给。
tools放 40px 的图标按钮,action放一个 主行动块,menu放下面的全屏菜单(它自己带着菜单钮)。子元素放在标志和工具之间,比如当前页的标题。 - 栏目本身不排在这一条里——那是"居中的顶部导航栏",这套语言不用;栏目收进全屏菜单。(云游戏页的顶栏里排着两个文字页签,但是靠左、紧跟着标志,不居中,见 云游戏页;这里仍把栏目收进全屏菜单。)
- 很窄的时候(360px 以下)四样东西放不下:先去掉
tools,把它们挪进全屏菜单的工具带。
全屏菜单#
点菜单钮后,一个白底的全屏面板盖住页面:
- 导航项:高约 56px,
surface-sunken底,项间留 3 – 4px 缝;内容是图标 + 竖向分隔线 + 文字 + 右侧箭头。 - 当前项整条变信号黄,图标与分隔线变墨色。
- 导航项下方:一排工具按钮(
surface-muted底)。 - 底部:一到两个通宽的深色按钮。
- 背景:一行镂空巨字,裁切在底部。
已实现为 <NavMenu trigger title brand tools footer ghost> + <NavMenuItem icon href current>:
| 部位 | 取值 |
|---|---|
| 面板 | 盖住整个视口,surface 底;在 --z-overlay 层,淡入(--duration-fast) |
| 栏目入场 | 逐条从左滑入 8px 并淡入,每条 300ms、ease-out;第一条等 100ms,之后每条晚 50ms,第六条起一起到(推断) |
| 最上面一行 | 高 56px,和顶栏一样;左边是 brand(没有就显示 title),右边是关闭钮 |
| 导航项 | 高 56px,surface-sunken 底,项间 4px;图标 20px + 一条 line-strong 的竖线 + 文字 + 行尾箭头 |
| 悬停 | surface-muted;按下 line |
| 当前项 | 整条 action 黄、on-action 字,图标、分隔线、箭头都变 on-action |
| 工具带 | 导航项下面一条 surface-muted 的带子,里面放图标按钮 |
| 底部 | footer,放一到两个主行动块,通宽 |
| 巨字 | ghost 给的一个短词,描边的 镂空字,贴着底部按钮的上沿,下四分之一裁掉 |
- 关闭钮正好落在菜单钮的位置上:最上面一行和顶栏等高、内边距相同,点开、关上手不用挪。
- 当前项不只靠黄色:亮色下黄和旁边的
surface-sunken明度很接近,灰度下几乎分不出来。所以另加粗体和左缘一条 4px 的墨色竖条,读屏另有aria-current="page"。 - 当前项的焦点环换了颜色,并且往里收:暗色主题下焦点色本身就是黄的,压在黄底上看不见,所以这一项的焦点环用
on-action,离边 2px——on-action是墨色,贴着边的话在暗色页面上和页面分不开,看上去只是黄条小了一圈;收进去之后环的两边都是黄。(往里收是推断) - 栏目是一条一条到的(推断,见 动效):整屏换掉是一次"入场",值得交代一下里面有什么。只有栏目这样,工具带、底部按钮、巨字跟着整层淡入。入场只动每一条的位置和透明度,不挡操作:焦点一打开就在第一个栏目上,栏目还没到齐键盘已经能用。关的时候整层淡出,不倒着播。
- 行为都是 弹窗 的:打开时焦点移入并被限制在内,背景不可滚动、不可交互;
Esc或关闭钮关上,焦点回到菜单钮。焦点先落在第一个栏目上,而不是关闭钮。 - 点一个栏目默认关菜单(单页应用里页面不会刷新,菜单不会自己消失);点了不该关的项(切换语言)加
closeOnClick={false}。 title是这块菜单的名称,必填。有brand时它只留给读屏。里面的栏目另是一个导航地标,名称默认"主导航"(navLabel)。- 工具带没有照上面写的把按钮本身做成
surface-muted:图标按钮的底是surface-sunken,直接放上去会和导航项连成一片。所以是带子用surface-muted,按钮保持原样压在带子上。 - 栏目很多、一屏放不下时,最上面一行不动,下面整段滚动。
页签#
在同一层级的几块内容之间切换(实测)。
| 项 | 官网 | 建议值 |
|---|---|---|
| 高度 | 3.75rem / 45px | 36 – 40px |
| 文字 | Medium 1.75rem | text-lg font-medium |
| 分隔线 | 0.1875 × 2.5rem,#D9D9D9 |
2px 宽,高为页签的三分之二 |
| 箭头 | 直径 2.3125rem 的 #FAFAFA 圆片,里面是 #999999 的箭头 |
高度的 60% |
| 圆角 | 4px | 0 |
| 状态 | 底 | 其他 |
|---|---|---|
| 默认 | 透明 | — |
| 悬停 | #F3F3F3 |
— |
| 激活 | #E5E5E5(实现取相邻的 surface-muted,#E6E6E6) |
文字左移约 1.75rem,右侧的箭头淡入 |
悬停底在实现里是 ink 以 5% 不透明度叠上去(hover:bg-ink/5),不用 surface-sunken:亮色下的结果约为 #F3F3F3,与实测一致;暗色下约为 #202020,比页面亮一档。surface-sunken 在暗色主题下是纯黑,用它会变成"悬停变暗、激活变亮",方向相反。
要点:
- 页签是一格一格的,之间用不通顶的竖线分隔,不是下划线式的。官网首页的格子有 4px 的微圆角,这里取直角。
- 云游戏页顶栏的"首页 / 更多"是下划线式的,可以拿来对照:CloudGame Medium 18px 的字,未选
#737373、悬停#BFBFBF、选中#EEEEEE;选中项下面一根和文字等宽的 2px 白线,线下居中一个朝下的 6 × 3px 小三角,换页签时这一组平移过去(0.2s,ease)——实测(云游戏页),见 云游戏页。这里的规则不变,也不加下划线的变体。 - 箭头在官网是一个圆片;这里画成了直角的方块(
surface-raised底)。 - 紧挨激活项的两条竖线不画,让给激活项的底色。
- 激活不用黄色,用浅灰底 + 位移。黄色留给更高层级的导航。
- 文字左移与箭头淡入同时发生,时长
--duration-base。 - 切过来的那一块面板淡入(
--duration-fast),只淡入、不位移:页签自己已经有"让位"的动作,面板再动就抢了。一开始就选中的那一块是静止的,切换过之后才有;三种变体都一样。(推断,见 动效) - 页签很多时横向滚动,不换行。
变体(推断):
| 变体 | 外观 | 用在哪 |
|---|---|---|
block |
上述的格子式 | 默认 |
capsule |
横向胶囊,选中反转为墨底白字;墨底是同一块,换一个时滑过去 | 筛选、类目切换(游戏界面的做法,社区) |
wedge |
选中项为黄底墨字、一侧斜切 | 游戏风格界面的一级页签(社区) |
三种都已实现,写法是 <Tabs variant>。
capsule 选中的底:
- 换一个时墨底滑过去(
--duration-fast),宽度跟着新的那一个变——胶囊各有各的宽,所以变的是宽度,不是缩放(缩放会把两端的圆角压扁)。字色同一拍换过来;aria-selected在选中的那一刻就变,不等它滑到。连着换时从半路改道。一开始选中的那一个是静止的;页签栏变了宽度、字体到了,都直接到位。(推断,见 动效) - 墨底盖在相邻胶囊的轮廓线上面,字在墨底上面。 滑过去的时候是一块完整的实心胶囊,不被路过的轮廓切开;所以胶囊页签里的字多包了一层(筛选胶囊没有)。(对照图比过,用户选的)
- 墨底画在页签栏的
::after上,位置是量出来的;页签多了横向滚动时它在滚动的内容里,跟着内容走。量不到的时候(服务端渲染、还没排版、藏着)选中的那一个照旧自己画墨底。 - 样式仍然和 筛选胶囊 是同一份;筛选胶囊是多选,各自开关,没有滑动的底。
- 格子式的"让位"和楔形是实测的做法,都没有滑动的底。
- 高对比模式下底色被系统换掉,墨底成了一片盖住选中胶囊轮廓的页面色,选中的反而少了一圈线:墨底和选中的胶囊改用系统的"选中"色(
Highlight底和边线、HighlightText字)。见 色彩。(推断)
wedge 的取值:
| 部位 | 取值 | 说明 |
|---|---|---|
| 选中项 | action 底、on-action 字,右侧斜切 |
楔形是 斜楔 的 wedge-r:左边贴边,右边斜切 10px |
| 未选项 | ink 字;悬停时出现同形的浅底(墨色 5%) |
— |
| 禁用项 | ink-disabled 字 |
— |
| 页签栏 | 底边一条 2px 的 ink 线 |
亮色页面上黄色对白底的明度差很小,楔形要坐在一条墨线上才立得住 |
- 高度两档:
md40px、sm36px。右侧的留白比左侧多 12px,让给斜边。 - 楔形画在按钮的
::before上,按钮本身不裁切:键盘聚焦时焦点环是完整的矩形。 - 选中项的焦点环换了颜色,并且往里收:环画在按钮里面,压在黄楔上。暗色主题下焦点色本身就是黄的,看不见,只在斜边外面露出一小截;所以选中项的环用
on-action(同 全屏菜单 的当前项),离边 2px——on-action是墨色,贴着边的话在暗色页面上和页面分不开,看上去只是黄楔小了一圈;收进去之后环的两边都是黄。未选项的环照旧是贴边的focus。(推断) - 语义与键盘和另外两种完全相同。
- 一个界面里只放一组:它是一级页签,二级的分类用格子式或胶囊。
- 高对比模式下黄底被系统换掉:楔形改用
Highlight、字用HighlightText,画在里面的焦点环也用HighlightText、再往里收 2px。格子式不用动——文字让位和箭头块在高对比模式下本来就看得出。见 色彩。(推断)
分页#
分页条#
用于列表的翻页(实测)。
| 项 | 官网 | 建议值 |
|---|---|---|
| 条高 | 5.25rem / 63px | 48 – 56px |
| 条的形状 | 胶囊(圆角 2.625rem) | 直角,见下面"实现"的第一条 |
| 条底 | #E6E6E6 叠 5% 斜纹 |
surface-muted + hatch |
| 翻页钮 | 直径 4.625rem 的圆钮,#FAFAFA,0 0 0.625rem 阴影 |
比条高小 8 – 12px |
| 页码 | Novecento Wide Medium 1.5rem | font-display text-lg |
翻页钮的状态:默认 #FAFAFA → 悬停信号黄 → 按下 #EEEA00;禁用时箭头变 #AAAAAA。
深色变体:条底 rgba(30,30,30,.8)。
页码写成 01 / 12,等宽数字。需要跳页时在页码处提供输入,而不是列出一长串数字按钮。
已实现为 <Pagination page pageCount onPageChange jump size>(page 从 1 起):
- 形状和官网不一样:官网是一条胶囊、两端是圆钮;这里是直角的条、两端是方钮(带 4px 微圆角,跟着图标按钮)。底色、斜纹、悬停变黄、按下和禁用的取值照官网。这份规范原先把官网的也写成了"方钮"——量的时候只记了宽高,没有看圆角——控件是照那样做的;2026-10-12 复查时改正了规范里的说法,控件的形状还没有跟着改。
- 条高两档:
md48px、lg56px,方钮各比条高小 8px;条底是surface-muted叠 斜纹。 - 方钮就是 图标按钮 的
accent变体,只是底换成surface-raised并加shadow-sm:默认的surface-sunken在暗色下是纯黑,压在深灰的条上像两个洞。悬停变信号黄、按下深一档、到头时禁用,都沿用图标按钮的。 - 斜纹的颜色从"黑 5%"改成了"墨色 5%":亮色下看不出差别,暗色下变成比条底亮一档的浅带——所谓深色变体不用单独写,跟着主题走。
- 页码至少两位、补零;总页数上百时位数跟着加。当前页加粗,总数降为
ink-secondary。 jump打开后当前页变成输入框:聚焦时全选,回车或失焦跳页,Esc还原;只收数字,越界的页码夹回范围内。- 读屏听到的不是
01 / 12,而是一句"第 1 页,共 12 页",翻页时用aria-live="polite"播报。 - 容器窄到 288px 以下时页码小一档,三位数的页码配跳页输入也不会折行。
- 高对比模式下条底和斜纹都不画,看不出这是一整条:条补一圈 1px 的
ButtonText线,画在内边距里,不占地方。两端的方钮有 图标按钮 自己的那一圈,到头而禁用的是GrayText。见 色彩。(推断)
胶囊导航器#
在少量条目间前后切换,并显示当前条目的名称(实测)。
- 近黑的胶囊(
#131315),高约 56px; - 两端各一个白色圆钮(
#FAFAFA); - 中间:小号的黄色计数(
1/4)+ 条目名称(白,常规字重)。
用在深色底上。浅色底上把胶囊换成 surface-muted,圆钮保持白色并加 shadow-sm。
已实现为 <Navigator items index onIndexChange loop size>(index 从 0 起,items 是各条目的名称):
- 胶囊在两个主题下都用
surface-muted:亮色是上面说的浅色做法;暗色下它是#282828,比页面亮一档——实测的#131315放在暗色页面(#141414)上会和页面融在一起。 - 圆钮是图标按钮的
floating变体,白底带阴影,不随主题变。 - 计数用
accent-ink:暗色下是信号黄;亮色下换成强调色的深档,黄色字在浅灰胶囊上读不出来。名称用ink。 - 高度两档:
md56px(圆钮 40px)、sm40px(圆钮 32px)。 - 翻动时按钮不跑位:所有条目的名称叠在同一格里,按最宽的那个定宽,只显示当前的一个。容器放不下时名称截断成省略号。
- 默认到头时对应的圆钮禁用;
loop打开后首尾相接。 - 计数与名称所在的区域是
aria-live="polite",读屏听到"第 2 项,共 4 项"加条目名;整个控件是一个分组,用aria-label说明翻的是什么。
进度短横#
一排短横线表示"共几项、在第几项",当前一根为黄(深色底)或墨色(浅色底)。只做指示,一般不可点击。
已实现为 <DashIndicator count index>(index 从 0 起):
- 短横 16 × 2px,间隔 4px,其余几根用
line-strong; - 当前一根用
accent-ink:暗色下是信号黄;亮色下不是墨色,而是强调色的深档——和必填标记、导航器的计数是同一条约定,黄色家族在两个主题下都认得出来; - 当前一根同时加长一倍,不只靠颜色区分;
- 容器放不下时各根等比收窄,不顶出容器;当前一根仍然是别的两倍长;
- 它是一张"图"(
role="img"),默认名称是"第 2 项,共 5 项";旁边已经有导航器播报位置时,给它加aria-hidden; - 压在深色版块或图像上时,给所在的容器加
data-theme="dark",短横自己不用换写法。 - 高对比模式下短横都是底色画的,整排会不见:当前一根用
Highlight,其余几根用CanvasText。见 色彩。(推断)
头像切换#
一列圆形头像里选一个,官网"干员情报"左侧的那一列(观察):
- 竖排的圆形头像,选中者带黄环;
- 上下各一个白色的圆形翻页钮,点一下换到上一个 / 下一个。
和胶囊导航器是同一类东西——在少量条目间切换——只是每个条目有一张脸,可以直接点。
已实现为 <AvatarSwitcher value onValueChange orientation size loop> + <AvatarSwitcherItem value label src>:
- 语义是单选组:每个头像里是一个真的单选按钮,名称是
label。方向键换人(到头会绕回去,这是原生单选组的行为)、Tab只停一次、带name时随表单提交,都是原生的。 - 选中者是 头像 的选中环;其余的降到 60% 的不透明度,悬停时恢复。选中不只靠那圈黄。
- 两端的翻页钮是图标按钮的
floating变体,白底带阴影,不随主题变。点了选上一个 / 下一个;到头时禁用,loop打开后首尾相接。翻页钮不占Tab顺序——键盘用方向键就够了。 - 头像多到放不下时,用
className给整个控件一个高度上限(横排时是宽度):头像在两个翻页钮之间滚动,选中的那个自动滚进来。 orientation="horizontal"是横排,翻页钮在左右两端。- 整个控件要有名称(
aria-label),说明选的是什么。 - 焦点环画在选中环的外面,隔 1px。
- 高对比模式下选中环换成画在头像里面的一圈
Highlight边线(见 头像),焦点环照旧在外面。
圆形翻页钮#
压在媒体角落的一对白色圆钮。见 按钮 的"图标按钮"。
面包屑#
显示层级路径。
// 档案 / 干员 / 详情- 双斜杠起头,单斜杠分隔(社区:游戏界面左上角的
// 模块 / 子页)。 - 斜杠用
ink-tertiary;末项加粗、不可点击。 - 字号
text-sm。 - 路径很长时,折叠中间项为
…。
已实现为 <Breadcrumb> + <BreadcrumbItem href>,最后一项加 current:
- 前面的项是
ink-secondary的链接,悬停变ink并出现下划线; - 斜杠都是装饰,对读屏隐藏;读屏听到的是一个名为"面包屑"的导航地标里的有序列表;
maxItems超出时保留首项和靠后的几项,中间折叠成一个…按钮;点击后展开全部,焦点交给第一个刚露出来的链接;- 容器太窄时整条换行,不横向滚动。
页头#
每个页面顶上的那一块:这是哪一页、怎么回去、在这一页能做什么。(推断:官网是整屏翻页的展示站,没有这样的页头。画法是把已有的几样摆到一起——返回钮是 按钮 back 变体的深灰斜纹底收成一个方块,微文字行同 分节标题 的 meta,面包屑、按钮都是现成的。)
// 站点 / 第七勘探队 / 档案
// ARCHIVE 共 128 条
[←] 十月第二批 [ 退回 ] [ 签收 ]
北区仓储站发出,三号管廊转运。| 项 | 值 |
|---|---|
| 结构 | 自上而下:面包屑 → 微文字行 → 标题行 → 标题下面的一行(页签、几个统计块) |
| 标题行 | 左边是返回方块、标题和它下面的说明;右边是行动区 |
| 微文字行 | font-tech、text-xs、ink-secondary,在标题上面 4px |
| 标题 | text-3xl font-bold,一行 48px 高;自身宽度不到 448px 时降到 text-2xl、一行 40px。长标题折行,不截断 |
| 说明 | text-sm,ink-secondary,标题下面 4px,最宽一行六十来个字 |
| 返回方块 | 40px 见方,4px 圆角,control 底叠中档斜纹,白色左箭头 20px;和标题之间 16px,对着标题第一行的正中 |
| 返回方块的状态 | 悬停 neutral-600,按下 control-pressed(同 back 按钮,实测);禁用是 disabled 底、不带斜纹 |
| 行动区 | 几个按钮,之间 8px;同样对着标题第一行的正中——标题折成几行、下面有没有说明都一样 |
| 各层之间 | 16px |
| 分隔 | 可选:底下一条 1px line,线上留 24px |
- 标题是页头自己画的,不套分节标题的灰色滑块和箭头——那是版块的标题,一页可以有好几个;页头一页一个。
- 按自身宽度响应(容器查询,同色带标题),不看视口:它可能在侧轨旁边的那一栏里。行动区放不下时整块折到标题下面、靠左;不会把标题挤成一个字一行。
- 面包屑和返回二选一就够。 层级深、要能跳回中间某一级的用面包屑;只有"回上一页"一个去处的用返回方块。两个都放不算错,但说的是同一件事。
- 返回方块里是直箭头,不是尖角:尖角在这套语言里是"翻页"(轮播和分页的圆钮)。
- 行动区里黄色的按钮仍然只能有一个(见 按钮)。
已实现为 <PageHeader title level meta description breadcrumb back actions divider sticky animate> + <PageHeaderBack href render>:
<PageHeader
breadcrumb={<Breadcrumb>…</Breadcrumb>}
back={<PageHeaderBack href="/archive" aria-label="返回档案" />}
meta="// ARCHIVE 共 128 条"
title="十月第二批"
description="北区仓储站发出,三号管廊转运。"
actions={<Button variant="action">签收</Button>}
/>- 语义是一个
<header>,标题是<h1>(level可改:页头用在抽屉、分栏里的时候不是一级标题)。 PageHeaderBack默认的可访问名称是"返回",建议用aria-label写成"返回某某"。传了href或render就是链接,否则是按钮(onClick里自己history.back())。back这一格也可以直接放一个带字的<Button variant="back" size="sm">。children是标题下面的那一行,放什么由使用方定。sticky:吸在滚动容器的顶上,带surface的实底和底下那条线,在--z-float这一层(内容之上、常驻导航和浮层之下)。上面还有吸顶的顶栏时用className把top改成顶栏的高度。页内目录 的offset让的就是它。- 入场(推断):挂上的时候播一次。微文字行 → 标题 → 说明,各自从左 8px 淡入归位(300ms),依次晚 100ms;没有微文字行时从标题起。不等进视口——页头在页面最上面,挂上的那一刻就在眼前。面包屑、返回方块、行动区和下面那一行不动:位置在动的东西不好点。
sticky时不播(它是一直贴在那儿的,不是"刚出现");animate={false}关掉。 - 没有做:标题滚出视口之后收成一条窄的;页头自带的页签(放进
children)。
步骤条#
一件事分几步做,告诉人一共几步、走到了哪一步。(观察 + 推断:宣传物料里的步骤是"黑色章头 + 黄色重点",见 宣传物料;已完成、当前、未到三态沿用 时间线 的节点。)
节点是一个 24px 的方块(章头),里面是两位的序号:
| 状态 | 节点 | 标题 |
|---|---|---|
| 已完成 | surface-inverse 底 + accent-ink-inverse 的勾 |
ink |
| 当前 | action 底 + on-action 的序号 + 1px ink 描边 |
ink,加粗 |
| 未到 | 1px line-strong 描边,空心,ink-secondary 的序号 |
ink-secondary |
| 有误 | 1px danger 描边 + danger 的警示图标 |
跟着它本来的状态 |
| 项 | 值 |
|---|---|
| 序号 | 01 02…,font-tech、等宽、text-xs |
| 连接线 | 2px;走过的一段 ink,没到的 line-strong;和节点之间留 8px,不从节点后面穿过。走完一步时墨色从起点一端充到头(300ms),往回走时收回去 |
| 标题 | text-sm font-medium;说明 text-xs、ink-secondary |
| 排法 | 横排:各步等宽,节点在左,线接在节点右边,标题和说明在节点下面左对齐 |
- 已完成是墨底黄勾,和选中的复选框是同一个逻辑;当前是黄块,描一圈 1px 的墨线——黄色对白底立不住,和时间线的当前节点一样。
- 三种状态是三种形状(勾、实心的数字块、空心的数字框),不只是三种颜色。
- 状态变的时候不硬切:连接线的墨色是充进去的,和 进度条 的充填是同一个画法;节点换色 200ms。节点里的序号换成勾是直接换的——那是内容,不是颜色。(推断,见 动效)
- 高对比模式下底色都被系统换掉:已完成的只剩一个勾浮着,当前的黄块成了空心框、和未到的只差序号的粗细,连接线整条不见。三种状态还得是三种形状:已完成是
CanvasText的实心块、勾用Canvas;当前是系统的"选中"色(Highlight的底和边线、HighlightText的序号);未到和有误的本来就是带描边的空心框,不动。连接线没到的收成 1px 的CanvasText细线,走过的还是 2px——粗对细,不靠两种系统色分。见 色彩。(推断) - 不居中。常见的步骤条把节点和文字都居中在每一栏里;这套语言里的东西贴左。
- 容器窄于 28rem 时自动改成竖排:节点在左、线往下接、文字在右。看的是所在容器的宽度,不是视口。
orientation="vertical"是一直竖排。 - 步骤多于五六个、或者不需要名字时,用 分段进度:它只说"第几步,共几步"。
已实现为 <Steps current orientation size> + <Step title description invalid href render onClick>:
current从 0 起,和导航器、进度短横一致;每一步的状态由它的位置算出来,不用逐个写。current等于步数时全部是已完成。- 列表是
<ol>,当前步带aria-current="step";节点和线对读屏隐藏,状态另有一句读屏才听得到的话("已完成""当前步骤""有误",statusLabels可换)。 - 传了
href、render或onClick的那一步可以点(回到做过的步骤):标题是链接或按钮,点击范围铺满这一步,焦点环画在这一步上。没传就是纯文字。 invalid标出"这一步有东西没填对":节点换成警示图标,状态里多一句"有误"。它不改变这一步是已完成还是当前。- 整个步骤条要有名称(
aria-label),说明是哪件事的步骤。
页内目录#
长页面旁边的一列锚点:这一页有哪几节、现在看到了哪一节。(推断:画法是 侧轨二级 的引线——一条细线,当前项那一段换成粗条)
| 项 | 值 |
|---|---|
| 引线 | 列表左缘一条 1px 的 line,从头贯到尾 |
| 项 | text-sm,ink-secondary;上下内边距 8px,一行约 36px 高;长标题折行,不截断 |
| 悬停 | 字变 ink。没有底色——它比导航轻 |
| 当前项 | 字变 ink,引线在这一项的这一段压着一段 3px 的 ink 粗条,比这一项上下各短 4px |
| 层级 | 最多三级,每深一级多缩进 12px;引线不跟着缩进 |
| 小标 | 可选的一行,在列表上面:font-tech、text-xs、ink-secondary |
- 当前项不加粗。 加粗会让字变宽,折行的位置跟着变,整列目录在滚动的时候上下跳。它靠的是粗条——形状上的差别,不只是字深了一档。
- 没有底色。粗条是同一段,沿引线滑到新的一项(
--duration-fast),长度跟着那一项的高度变——折了行的项更高。滚得快、当前项连着变的时候从半路改道,不排队。还没滚到第一节时粗条淡出,再亮起来是原地淡入,不从上一个位置滑来;载入时就亮着的那一项是静止的。(推断,见 动效) - 它是"这一页里的位置",不是"站点里的位置":当前项用
aria-current="location",不是"page"。
已实现为 <Toc title offset target onActiveChange> + <TocItem href section level render>:
<Toc title="// 本页" className="sticky top-6">
<TocItem href="#bulletin">最新情报</TocItem>
<TocItem href="#fieldwork">日常作业</TocItem>
<TocItem href="#crew" level={2}>
队员
</TocItem>
</Toc>- 哪一项是当前的:最后一个"顶边已经到了视口上沿"的小节。页面滚到底时是最后一项——最后一节往往很短,顶边永远到不了上沿。还没滚到第一节时,哪一项都不亮。
offset:上沿往下让多少像素。页面有吸顶的页头时传页头的高度;同时给各节的标题加同样大小的scroll-mt-*,点目录跳过去的时候标题才不会被页头压住。- 点一项仍然是点一个锚点链接:地址、后退、
scroll-mt都是浏览器原生的那一套,控件不自己滚。它只让这一次跳转滚得平滑——点的那一下给滚动容器和<html>临时写上scroll-behavior: smooth,滚动停下就还原成原来的值;系统开了"减少动态效果"时不写,直接跳(和 回到顶部 一样)。(推断,见 动效) - 点了之后这一项立刻是当前项,并且保持到这一次滚动停下、使用者自己再滚为止——否则点倒数第二节,它到不了上沿,亮起来的会是别的;滚过去的途中,粗条也不会一项一项闪过去。
- 小节是按
href里#后面的id找的;地址在路由库的链接组件手里(render)时,用section直接给id。 - 默认看整个页面的滚动;内容在某个滚动容器里时传
target(那个容器的ref)。 - 位置由使用方给:通常是
className="sticky top-6"。目录比视口还高时再给它一个高度上限和overflow-auto,这时当前项会保持在看得见的范围里——动的只是目录自己的滚动位置,不碰页面。 - 语义是一个
<nav>,以小标命名(没有小标时是"本页目录",用aria-label换);里面是一个平的列表,层级只是缩进。 - 粗条画在列表的
::after上,位置是量出来的。量不到的时候(服务端渲染、还没排版、目录藏着)当前项照旧自己画那一段,不会有哪一帧没有粗条。aria-current在当前项变的那一刻就改,不等粗条滑到。 - 高对比模式下粗条是底色画的,会不见,而各项都是链接、字色一样:粗条改用系统的"选中"色(
Highlight)。引线是装饰,不另画。见 色彩。(推断) - 窄屏放不下时由使用方收起来(藏掉、换成一个下拉)。控件不替你决定——那取决于页面。
回到顶部#
长页面滚下去之后,角上出现的一个钮,点了回到最上面。(推断:官网是整屏翻页的展示站,没有这个控件。层叠 里的 --z-float 这一层是给它留的。)
| 项 | 值 | 来源 |
|---|---|---|
| 形状 | 直角方块,48px(sm 40px) |
推断:圆钮在这套语言里是"翻页" |
| 默认 | surface-inverse 底、ink-inverse 的上箭头、shadow-md |
推断 |
| 悬停 | action 底、on-action 箭头 |
分页条的翻页钮(实测):悬停变信号黄 |
| 按下 | action-pressed |
同上 |
| 位置 | 视口右下角,离边 16px,让开安全区 | 建议值 |
| 层级 | --z-float:在内容之上,在常驻导航和浮层之下 |
— |
- 用反转填充,不用
surface-raised加阴影:它浮在任意内容上,反转的底在两个主题下都和页面拉得开;白底配阴影在暗色页面上是一块融进去的深灰。 - 滚过一段才出现(默认 400px)。没出现的时候不占
Tab顺序,读屏也读不到。 - 点了平滑滚回顶部;系统开了"减少动态效果"时直接跳过去。
- 焦点跟着回去。 滚到顶之后这个钮自己会消失,焦点如果留在它身上就丢了,下一次
Tab会从页尾开始。所以点了之后焦点交给滚动容器(整页时是<body>),下一次Tab从页面的头上开始。 - 箭头是 图标 里的直箭头转了 90°,没有加新图标。
已实现为 <BackToTop target threshold size>:
- 默认看整个页面的滚动,自己
fixed在视口右下角; - 要放进某个滚动容器时传
target(那个容器的ref),并用className把位置改成相对那个容器的(比如absolute right-3 bottom-3,容器外面包一层relative); fixed相对的不一定是视口。 祖先上有transform、filter或容器查询(@container)时,fixed相对的是那个祖先。这时把它放在那一栏的最后,位置改成sticky(className="sticky bottom-4 self-end"):它贴着视口的下沿跟着走,滚到这一栏的末尾时停在原位;- 默认的可访问名称是"回到顶部",用
aria-label换; - 和 滚动提示 正好相反:那个只在"还没开始滚"的时候出现,这个只在"已经滚远了"的时候出现。两个可以同时放。
行为与无障碍#
- 导航容器用
<nav>并加aria-label。 - 当前项加
aria-current="page"。 - 页签用
role="tablist"/tab/tabpanel,方向键切换。 - 分页按钮有明确的可访问名称("上一页""下一页"),禁用时用
disabled。 - 全屏菜单打开时锁定背景滚动、把焦点移入菜单,
Esc关闭并把焦点还给菜单钮。 - 侧轨收起时每一项仍然有文字名称(视觉隐藏),浮出来的栏目名对读屏隐藏,不重复朗读。
- 胶囊导航器的计数变化、分页条的页码变化都用
aria-live="polite"播报。
宜 / 忌#
宜
- 让导航贴边、尽量窄。
- 用填充或边条表示当前项。
- 桌面用侧轨,窄屏换顶栏 + 全屏菜单——重新设计,而不是把侧轨缩小。
忌
- 居中的顶部导航栏配下划线。忌的是"居中":云游戏页的顶栏靠左,文字页签配一根滑动的下划线,见 云游戏页;库里的页签不加下划线的变体。
- 当前项只靠文字加粗或颜色变化。
- 图标导航没有任何文字替代。
- 页签用圆角胶囊配发光。
- 分页列出几十个数字按钮。
来源#
- 全部尺寸与状态:measurements.md 第 8.1、8.2、8.6、8.7 节。
- 胶囊页签、斜切页签、面包屑写法:Statrue/endfield-design-skill。
- 云游戏页的顶栏、文字页签、导航项和"更多"的目录:measurements-cloud.md 第 8.1、8.2、8.8 节;和规范的对照见 云游戏页。