## 品牌标识(本次会话) 起因:品牌此前没有任何图形标识 —— 唯一 favicon 是内联 data-URI 里的字母「A」, 那是 v2.0.0「Aurora Admin → Kole UI」改名漏掉的一处(PC 顶栏也是「A」, 移动端站已是「K」;移动端文档站则完全没有 favicon)。 - 几何:24 网格三个互不接触的笔画(竖 + 两斜),圆头描边; 描边 2.25 → 16px 标签页尺寸下正好 1.5px = 规范原文「描边1.5px」 - 取色分两套(刻意):favicon 硬编码品牌蓝/白(渲染在浏览器标签栏,不继承 kole-dark); 顶栏标记走 currentColor(实测暗色下自动转 rgb(20,22,28)) - 新增 theme-color 双条(light #FFFFFF / dark #1C1F26,取 --kole-color-card-bg) - 修 site/app.js hero 标语 KOLE ADMIN → KOLE UI(改名变形残留) - 移动端 7 个模板补 favicon(此前计数 0) 验收:门禁 9 条全 OK(site-routing/site-routes/mobile-docs/mobile-site/isolation/ theme/nav/i18n/icons);PC 回归 1464/1464 · 移动端 807/807,各连跑 8 次一致; 两端 favicon 405 字节逐字节一致;PC 站控制台错误 1→0。 ## 并行会话成果(本次一并入库) - 图标系统:2576 图标(TDesign/Element Plus,MIT)+ 11 端注入 + 5 个构建门禁工具 + IconPreview 预览页 + ICON-SPEC.md 冻结规格 - 移动端平台:47 组件 × 6 端 + 文档站 53 页 + 隔离门禁 - PC 组件:103 个大后台组件 / 组件11 批次 - uni-app:PC 端试点 + 移动端端实现 + 真实编译验证 ## 工程 - .gitignore 补 .scratch/ 与 .zcode-preexisting-*.txt(会话中间产物,实测 9.1MB,不入库) - CHANGELOG 补品牌标识条目 - ROADMAP 登 S8-P4(品牌标识任务包 + og:image/apple-touch-icon 未做部分)
107 KiB
Kole UI · 移动端规格(Mobile Spec)
文档性质:移动端组件的规格原文,与本仓库
组件1~10.txt(PC 端)同级 —— 契约 JSON 的 usageHints / anatomy / doNotInvent 必须逐字来自本文件,不得发明。 来源声明:PC 端规格来自外部交付的组件1~10.txt;移动端没有外部交付的规格原文, 本文件由本仓库撰写并作为唯一授权来源(sourceKind: "authored-spec")。 因此移动端契约的provenance字段必须写明authored-in-repo,不得声称来自组件*.txt。 分支:feat/s6-p21-component-families之后的 S7 阶段。
〇 · 隔离总则(先读)
移动端与 PC 端原料隔离、令牌同源:
| 维度 | PC 端 | 移动端 | 是否共享 |
|---|---|---|---|
| 实现目录 | frameworks/ |
frameworks-mobile/ |
否 |
| 契约 | .design_library/kole-ui/components/ |
.design_library/kole-ui-mobile/components/ |
否 |
| 类名前缀 | kole- |
kole-m- |
否 |
| 令牌前缀 | --kole- |
--kole-m- |
颜色/字体/圆角/阴影共享(@import PC 令牌文件) |
| 导出名 | KoleButton |
KoleMNavBar |
否 |
| 测试页 | tests/<slug>.html |
tests/mobile/<slug>.html |
否 |
| 回归报告 | tests/report.json |
tests/mobile-report.json |
否 |
| 文档站 | site/components/、SPA 路由 |
site/m/(静态站,不进 SPA 路由表) |
否 |
硬规则:
- 移动端实现不得出现在
frameworks/;PC 实现不得出现在frameworks-mobile/。 - 移动端样式只能引用
--kole-*或--kole-m-*令牌,不得出现硬编码十六进制颜色。 - 移动端类名只能是
kole-m-<name>或状态类is-<state>。 - PC 侧任何文件不得引用
frameworks-mobile/或site/m/;移动端不得引用frameworks/。
1 · 顶部导航栏 NavBar
1.1 用途
页面顶部的标题栏,提供返回入口、页面标题与右侧操作区。
1.2 结构(anatomy)
bar:导航栏容器,固定于页面顶部,高度 44px + 顶部安全区back:左侧返回入口,可为返回箭头或文字title:中间标题,单行省略actions:右侧操作区,可放 1~2 个图标按钮或一个文字按钮divider:可选底部分隔线
1.3 变体维度
titleAlign:center(标题居中,左侧仅图标) /left(标题左对齐,紧跟返回)elevated:false(无阴影) /true(滚动后投影)
1.4 状态
- default:常态
- scrolled:页面滚动后出现分隔线或投影
- disabled:右侧操作不可用(按钮置灰)
1.5 交互与触控
- 返回入口与右侧操作的点击热区不小于 44×44,视觉图标可小于该尺寸
- 标题超长时单行省略,不换行、不撑开栏高
- 顶部内边距包含安全区,横屏与刘海屏不遮挡内容
1.6 无障碍
- 容器语义
role="banner"(页内使用时也可用role="navigation"并配aria-label) - 标题节点具备
aria-label或可见文本 - 图标按钮具备
aria-label
1.7 doNotInvent
- 多行标题的折叠规则
- 返回行为的栈深度策略(是否回退到首页)
1.8 unknowns
- 阴影出现的确切滚动阈值
- 右侧操作超过 2 个时的收敛方式
2 · 底部标签栏 TabBar
2.1 用途
底部主导航,2~5 个标签页之间切换,是移动端一级导航。
2.2 结构(anatomy)
bar:标签栏容器,固定于页面底部,高度 50px + 底部安全区item:单个标签,含图标与文字icon:图标,可为内联 SVGlabel:标签文字,11pxbadge:角标,可为数字或红点
2.3 变体维度
count:2/3/4/5(标签数量,超过 5 项应改用其它导航形态)badge:none/dot/number
2.4 状态
- active:当前选中项,颜色为品牌色
- inactive:未选中项,颜色为次要文字色
- disabled:该项不可点击(置灰且不响应)
2.5 交互与触控
- 每项点击热区等分整栏宽度,高度不小于 44px
- 选中项切换后
aria-selected同步变化 - 底部内边距包含安全区
2.6 无障碍
- 容器
role="tablist",单项role="tab" - 选中项
aria-selected="true",未选中false - 每项具备
aria-label(图标 + 文字时可用文字代替)
2.7 doNotInvent
- 标签项超过 5 个时的滚动或折叠规则
- 图标资源的成套规则(规格只约定尺寸与语义)
2.8 unknowns
- 角标超过两位数的收敛(如 99+)
- 选中态是否带图标填充切换
3 · 动作面板 ActionSheet
3.1 用途
从底部弹出的操作列表,用于在少量互斥操作中做一次选择。
3.2 结构(anatomy)
mask:遮罩,点击关闭panel:面板容器,自底部滑出title:可选标题说明action:操作项,高度 56px,可标记危险操作cancel:底部取消按钮,与操作项之间有间隔
3.3 变体维度
tone:default/danger(危险项用错误色)cancel:inline(取消作为普通项) /separate(取消独立成块)
3.4 状态
- closed:面板收起(默认)
- open:面板展开,遮罩可见
- disabled:单个操作项不可用(置灰、点击无效)
3.5 交互与触控
- 点击遮罩关闭;点击操作项或取消后关闭
- 面板滑出动画 240ms,缓动曲线
cubic-bezier(.32,.72,0,1) - 操作项点击热区高度不小于 56px
3.6 无障碍
- 面板
role="dialog"+aria-modal="true" - 遮罩为纯装饰,不参与焦点
- 操作项为原生
button,危险项带aria-label说明
3.7 doNotInvent
- 多级面板的堆叠规则
- 手势下滑关闭的触发阈值
3.8 unknowns
- 操作项超过多少条时需要内部滚动
- 危险项是否需要二次确认
4 · 下拉刷新 PullRefresh
4.1 用途
列表顶部下拉手势触发刷新,移动端最常见的列表刷新入口。
4.2 结构(anatomy)
viewport:包裹滚动内容的容器,负责手势indicator:下拉指示区,含箭头或旋转图标与状态文字content:业务内容
4.3 变体维度
state:pull(下拉中) /ready(已达阈值) /refreshing(刷新中) /done(完成提示)threshold:触发阈值,默认 60px
4.4 状态
- pull:下拉未达阈值,指示器随位移旋转
- ready:达到阈值,提示「松开立即刷新」
- refreshing:刷新中,指示器旋转,下拉不回弹
- disabled:手势失效(如刷新中再次下拉)
4.5 交互与触控
- 手势使用 Pointer Events,位移以纵向为主;横向位移更大时让位给页面横滑
- 达到阈值后松手进入 refreshing;未达阈值松手回弹
- 刷新期间再次下拉不重复触发
4.6 无障碍
- 指示区
role="status"+aria-live="polite",状态文字变化被读屏播报 - 需保留一个非手势的等价入口(如列表底部的刷新按钮)
4.7 doNotInvent
- 惯性与阻尼曲线
- 与页面整体下拉(浏览器级)的竞争规则
4.8 unknowns
- 刷新超时的提示形式
- 完成提示的停留时长
5 · 滑动单元格 SwipeCell
5.1 用途
列表行左滑露出操作按钮,用于删除、标记等单行操作。
5.2 结构(anatomy)
cell:可滑动的行容器content:行内容(标题、描述)actions:右侧操作区,随滑动露出action:单个操作按钮,可标记危险
5.3 变体维度
direction:left(左滑露出右侧操作,默认) /right(右滑露出左侧操作)actions:1/2(操作数量,最多 2 个)
5.4 状态
- closed:未滑动(默认)
- open:已滑出,操作区可见
- dragging:拖拽中
5.5 交互与触控
- 横向位移超过 10px 判定为滑动,纵向位移更大时取消滑动
- 松手后按位移是否超过操作区宽度的一半决定展开或回弹
- 展开状态下点击内容区先收起,不触发内容点击
5.6 无障碍
- 操作按钮为原生
button,具备aria-label - 滑动不可作为唯一路径:操作按钮在展开后必须可键盘聚焦
5.7 doNotInvent
- 多行同时展开的互斥规则
- 滑动与纵向滚动的竞争阈值细节
5.8 unknowns
- 操作区宽度是否有标准档位
- 拖拽中的阴影表现
6 · 按钮 Button
6.1 用途
触发一个即时动作。移动端按钮要比桌面端更"敢按":点击热区不小于 44px,主次层级靠颜色与边框区分。
6.2 结构(anatomy)
button:根元素,用原生button,圆角取令牌label:按钮文字,单行不换行icon:可选图标或加载指示器,与文字间距 4pxblock:可选块级形态,撑满容器宽度
6.3 变体维度
type:primary/default/text/dangersize:large(44px 高,移动端默认)/default(36px)/small(28px)block:false/true
6.4 状态
- default:常态
- active:按下时背景加深(移动端没有 hover,反馈靠
:active) - disabled:置灰且不响应点击
- loading:显示加载指示器并阻止重复触发
6.5 交互与触控
- 高度不小于 44px 的档位用于主操作区;小尺寸档只用于行列内联操作
- 按下反馈用
:active背景色变化,不做位移缩放(避免长按抖动) - loading 期间重复点击不触发第二次事件
6.6 无障碍
- 使用原生
button,天然可聚焦、可键盘触发 - 仅有图标时必须给
aria-label - loading 时置
aria-busy="true"且disabled(避免重复提交)
6.7 doNotInvent
- 按钮内多行文字的排版规则
- 长按(long-press)的附加行为
6.8 unknowns
- 图标与文字同时存在时的最小宽度
- 危险按钮是否需要二次确认
7 · 单元格 Cell
7.1 用途
列表的基本单元:一行里承载"标题 + 说明 + 值 + 箭头",可整行点击进入下级。
7.2 结构(anatomy)
cell:根元素,一行两类内容(左侧主区 / 右侧值区)title:主标题,单行省略desc:可选副标题,单行省略value:右侧值或状态文字arrow:可选右箭头,表示可进入icon:可选左侧图标
7.3 变体维度
arrow:false/truelink:false(纯展示)/true(整行可点)
7.4 状态
- default:常态
- active:按下时整行背景变化
- disabled:置灰且不响应
7.5 交互与触控
- 可点单元格整行都是热区,高度不小于 56px
- 按下反馈是整行背景变化,不是只有文字变色
- 单元格之间默认有 1px 分隔线,可用
borderless关掉
7.6 无障碍
- 可点单元格用原生
button;纯展示单元格用div - 值区只作展示时不要放进可聚焦元素
7.7 doNotInvent
- 多行标题的折叠规则
- 单元格内多列布局的栅格规则
7.8 unknowns
- 副标题最多显示几行
- 右侧值超长时的截断策略
8 · 分割线 Divider
8.1 用途
在内容之间画一条细分隔线;带文字时用于分区标题。
8.2 结构(anatomy)
divider:根元素,水平时为 1px 高text:可选文字,居中或左/右对齐line:文字两侧的线
8.3 变体维度
orientation:horizontal/verticalalign:left/center/right(带文字时生效)dashed:false/true
8.4 状态
- 静态组件,无交互状态(这是设计如此,不是缺失)
8.5 交互与触控
- 纯静态,不参与点击与手势
- 垂直分割线高度跟随父容器(用
align-self: stretch)
8.6 无障碍
- 纯装饰:
role="separator"+aria-hidden="true"(对读屏无信息量) - 带文字的分割线不隐藏,文字本身即语义
8.7 doNotInvent
- 主题化分割线(渐变、图片)的表现
8.8 unknowns
- 垂直分割线的推荐间距
9 · 徽标 Badge
9.1 用途
在图标或头像右上角标出数量或状态(红点 / 数字 / 短文字)。
9.2 结构(anatomy)
badge:根元素,可包裹子元素(角标形态)也可独立使用count:数字或短文字dot:红点形态(无内容)wrap:被包裹的内容(图标 / 头像 / 按钮)
9.3 变体维度
shape:dot/number/textstandalone:false(包裹在子元素上)/true(独立使用)
9.4 状态
- default:常态
- overflow:数值超过
max时显示max+ - hidden:数值为 0 且未开启
showZero时隐藏
9.5 交互与触控
- 角标本身不可点(可点的是被包裹的元素)
- 角标不改变被包裹元素的布局尺寸(绝对定位)
9.6 无障碍
- 数字角标配
aria-label(如「12 条未读」),否则读屏只读出数字 - 纯装饰红点
aria-hidden="true"
9.7 doNotInvent
- 角标内容的动画(出现 / 消失)
9.8 unknowns
max的默认取值(本实现取 99)- 独立使用时是否需要背景色
10 · 标签 Tag
10.1 用途
用简短的文字标注状态、分类或属性;可关闭的标签用于已选项。
10.2 结构(anatomy)
tag:根元素,圆角胶囊label:标签文字close:可选关闭按钮
10.3 变体维度
tone:default/primary/success/warning/dangersize:default/smallclosable:false/true
10.4 状态
- default:常态
- disabled:置灰且不可关闭
- closed:已关闭(由宿主从列表里移除)
10.5 交互与触控
- 关闭按钮热区不小于 24×24(小尺寸标签内用负外边距扩展热区)
- 关闭动作只触发一次事件,是否真的移除由宿主决定
10.6 无障碍
- 关闭按钮为原生
button并带aria-label(如「移除标签:已发货」) - 纯展示标签不加交互角色
10.7 doNotInvent
- 标签的动态增删动画
- 超长标签的换行规则
10.8 unknowns
- 同一行最多放几个标签
- 关闭后是否保留占位
11 · 弹出层 Popup
11.1 用途
从指定方向弹出的浮层基座,承载面板、抽屉、动作列表等内容。
11.2 结构(anatomy)
mask:遮罩,点击关闭(可关)popup:浮层容器,按方向定位header:可选标题区body:内容区,可滚动close:可选关闭按钮
11.3 变体维度
placement:center/bottom/top/left/rightround:false/true(贴边方向在靠内容一侧切圆角)
11.4 状态
- closed:收起(默认)
- open:展开,遮罩可见
- dragging:预留(本实现未做拖拽关闭)
11.5 交互与触控
- 遮罩点击关闭;
closeOnMask=false时不关闭 - 滑入动画 240ms,缓动
cubic-bezier(.32,.72,0,1) - 内容超出时
body内部滚动,遮罩不滚动
11.6 无障碍
- 浮层
role="dialog"+aria-modal="true" - 遮罩
aria-hidden="true"(纯装饰) - 关闭按钮为原生
button并带aria-label
11.7 doNotInvent
- 多浮层堆叠的层级规则
- 手势下滑关闭的阈值
11.8 unknowns
- 各方向的内容最大尺寸
- 是否需要焦点陷阱(focus trap)
12 · 轻提示 Toast
12.1 用途
在屏幕中央或底部短暂提示一条结果信息(成功 / 失败 / 警告 / 加载中),不打断当前操作。
12.2 结构(anatomy)
toast:浮层本体,居中于视口icon:状态图标(可选)text:提示文字mask:可选透明遮罩(mask=true时阻断下方点击)
12.3 变体维度
tone:info/success/warning/danger/loadingposition:center/bottom/topmask:false/true(阻断交互)
12.4 状态
- hidden:收起(默认,靠
is-open切换) - open:展开可见
- loading:
tone=loading时图标持续旋转(prefers-reduced-motion下降级为静止)
12.5 交互与触控
- 提示本身不可点(不抢焦点、不阻断),
mask=true时遮罩吸收手势 - 出现 / 消失动画 240ms;自动关闭时长由宿主控制(本组件只负责显示态)
12.6 无障碍
- 容器
role="status"+aria-live="polite"(结果朗读一次,不反复打断) tone=loading时补aria-busy="true"- 图标为装饰(
aria-hidden="true"),语义全部由文字承担
12.7 doNotInvent
- 自动关闭的默认时长
- 多条提示的排队 / 合并策略
12.8 unknowns
- 单行文字的最大宽度与换行规则
- 是否需要点击穿透设置
13 · 对话框 Dialog
13.1 用途
需要用户确认或输入的中断式浮层:标题 + 内容 + 操作按钮组。
13.2 结构(anatomy)
mask:遮罩,点击可关(可配置)dialog:对话框本体,居中header:标题区body:内容区footer:操作按钮组(取消 / 确认)
13.3 变体维度
variant:confirm(确认框)/alert(提示框,只有一个按钮)tone:default/danger(确认按钮用错误色)round:false/true
13.4 状态
- closed:收起(默认)
- open:展开,遮罩可见
- loading:确认按钮进入加载态并禁用(由宿主传入)
13.5 交互与触控
- 遮罩点击关闭;
closeOnMask=false时不关闭 Esc关闭(键盘可达时)- 按钮热区不小于 44px;操作按钮等宽排列
13.6 无障碍
- 对话框
role="dialog"+aria-modal="true"+aria-labelledby指向标题 - 打开后焦点落在对话框内(本实现只标记
tabindex="-1"+role,焦点陷阱见 §13.7) - 遮罩
aria-hidden="true"
13.7 doNotInvent
- 焦点陷阱(focus trap)的完整实现
- 多对话框嵌套时的层级规则
13.8 unknowns
- 对话框的最大宽度与最大高度
- 长内容是否在 body 内滚动
14 · 宫格 Grid
14.1 用途
把图标 / 文字入口按等分列排成网格,用于首页功能入口区。
14.2 结构(anatomy)
grid:根容器grid__item:单个格子(图标 + 文字)grid__icon:图标区grid__text:文字标签
14.3 变体维度
columns:2/3/4(每行格数)border:false/true(是否画格线)square:false/true(格子是否为正方形)
14.4 状态
- default:常态
- active:按下反馈(
:active底色变化) - disabled:置灰且不可点
14.5 交互与触控
- 每个格子整块可点,热区不小于 44×44
- 按下反馈用
:active(不用:hover—— 触屏没有悬停)
14.6 无障碍
- 可点格子用原生
button(整块热区 + 键盘可达) - 纯展示格子用
div且不加交互角色 - 图标装饰(
aria-hidden="true"),文字即语义
14.7 doNotInvent
- 格子的拖拽排序
- 超出 4 列的响应式折行列数
14.8 unknowns
- 图标区的推荐尺寸
- 一格最多几个字
15 · 步骤条 Steps
15.1 用途
横向展示多步流程的当前进度(如「提交 → 审核 → 完成」)。
15.2 结构(anatomy)
steps:根容器,横向排列steps__item:单个步骤steps__dot:序号点 / 勾选标记steps__label:步骤标题steps__line:连接线
15.3 变体维度
direction:horizontal/verticalstatus:wait(未开始)/process(进行中)/finish(已完成)/error
15.4 状态
- wait:灰色,未开始
- process:品牌色,进行中
- finish:品牌色 + 勾选
- error:错误色
15.5 交互与触控
- 纯展示组件,步骤本身不可点(这是设计如此,不是缺失)
- 步骤过多时横向可滚动(容器
overflow-x: auto)
15.6 无障碍
- 容器
role="list",每步role="listitem" aria-current="step"标出当前步- 状态不只靠颜色(进行中加粗 + 已完成用勾选字符)
15.7 doNotInvent
- 步骤之间的动画过渡
- 点击步骤跳转的规则
15.8 unknowns
- 纵向步骤条的推荐间距
- 标题最多几行
16 · 通知栏 NoticeBar
16.1 用途
在页面顶部或内容区之间横向滚动展示一条通告(如「系统维护通知」)。
16.2 结构(anatomy)
noticebar:根容器noticebar__icon:左侧喇叭图标noticebar__text:通告文字(可滚动)noticebar__close:可选关闭按钮
16.3 变体维度
tone:default/success/warning/dangerscrollable:false/true(文字超宽时是否跑马灯)closable:false/true
16.4 状态
- default:常态
- scrollable:文字持续横向滚动
- closed:已关闭(由宿主移除)
16.5 交互与触控
- 关闭按钮热区不小于 24×24(用负外边距扩展)
- 跑马灯在
prefers-reduced-motion: reduce下停止滚动并改为换行显示(无障碍硬要求) - 关闭动作只触发一次事件,是否移除由宿主决定
16.6 无障碍
- 容器
role="status"(读屏会朗读一次通告) - 关闭按钮为原生
button并带aria-label - 跑马灯不得用
aria-live(滚动内容反复朗读会干扰)
16.7 doNotInvent
- 多条通告的排队 / 轮播
- 滚动速度的可配置项
16.8 unknowns
- 通告文字的最大长度
- 是否支持富文本
17 · 数字键盘 NumberKeyboard
17.1 用途
为金额、验证码等纯数字输入提供自绘键盘(比系统键盘更可控、更安全)。
17.2 结构(anatomy)
keyboard:根容器,固定在底部keyboard__key:单个按键keyboard__key--delete:删除键keyboard__confirm:确认键(type=confirm时)
17.3 变体维度
type:number(0-9 + 小数点)/digit(纯 0-9)showDelete:false/trueshowConfirm:false/true
17.4 状态
- default:常态
- active:按下反馈
- disabled:禁用(确认键不可用)
17.5 交互与触控
- 按键热区不小于 44×44,网格等分排列
- 按下用
:active,不做悬停态 - 键盘本身不持有输入值 —— 只 emit 按键事件,由宿主决定写入哪个输入框
17.6 无障碍
- 容器
role="group"+aria-label - 每个按键为原生
button,文字即按键名(读屏读「1」「删除」) - 删除键用
aria-label="删除"(不读成符号)
17.7 doNotInvent
- 键盘高度的手势拖拽调整
- 与系统键盘的互斥逻辑
17.8 unknowns
- 是否支持自定义按键顺序
- 长按连续删除的间隔
18 · 日期选择器 DatePicker
18.1 用途
从底部弹出的年 / 月 / 日选择器,用于生日、有效期这类需要日期输入的场景。
18.2 结构(anatomy)
mask:遮罩,点击关闭picker:底部浮层picker__header:取消 / 标题 / 确定picker__columns:年 / 月 / 日三列picker__column:单列,可滚动picker__option:单个选项
18.3 变体维度
mode:date(年月日)/month(年月)round:false/true
18.4 状态
- closed:收起(默认)
- open:展开,遮罩可见
- selected:当前选中项(品牌色高亮)
18.5 交互与触控
- 遮罩点击关闭;
closeOnMask=false时不关闭 - 选项行高不小于 44px,滚动容器
-webkit-overflow-scrolling: touch - 确定 / 取消按钮热区不小于 44px
18.6 无障碍
- 浮层
role="dialog"+aria-modal="true" - 每列
role="listbox",选项role="option"+aria-selected - 遮罩
aria-hidden="true" - 选中值以
YYYY-MM-DD文本呈现(不依赖视觉滚动位置)
18.7 doNotInvent
- 日期范围的禁用规则(由宿主传入)
- 滚轮惯性 / 吸附动画的物理参数
18.8 unknowns
- 可选年份的范围
- 是否支持「至今」这类特殊选项
19 · 图标 Icon
19.1 用途
用一个字形表达状态或动作(选中、警告、返回、更多)。移动端与桌面端的差别在尺寸基线:手指操作要求图标更大、且图标自身从不承担点击 —— 点击由包裹它的 44px 按钮提供。
19.2 结构(anatomy)
icon:根元素,一个固定边长的内联盒子,负责尺寸与颜色glyph:字形节点,纯符号或内联 SVG,不带语义label:可选的无障碍名称,有值时图标成为「有语义的图」,无值时对读屏隐藏size:四档边长(small 16 / default 20 / large 24 / xlarge 32)tone:颜色来源(继承父级或取语义色)
19.3 变体维度
size:small/default/large/xlargetone:default/brand/secondary/dangerspin:false/true
19.4 状态
- default:常态(静态图)
- spinning:持续旋转,仅表示「进行中」,不表示成功
- disabled:无独立禁用态 —— 由父级按钮 / 单元格置灰,图标继承其颜色
19.5 交互与触控
- 图标自身不是热区;可点时由父级按钮提供不小于 44×44 的点击区
- 图标与相邻文字的间距取 4px(与按钮内图标一致)
spin=true在prefers-reduced-motion: reduce下停止旋转(无障碍硬要求)- 同一行内图标与文字基线对齐(
vertical-align: -0.125em),避免文字被顶高
19.6 无障碍
label有值时role="img"+aria-label,读屏读出该名称label为空时aria-hidden="true",纯装饰不进读屏序列- 图标不得作为唯一信息载体:颜色变化必须伴随文字或 label
- 语义色图标与背景的对比度不低于 3:1(WCAG 1.4.11 非文本对比)
19.7 doNotInvent
- 图标资源清单(本仓库不引图标字体,字形由各端内置表或内联 SVG 提供)
- 图标更换 / 过渡动画
- 点击图标自身触发动作
19.8 unknowns
- 业务侧自定义字形的注册方式
- 图标与文字组合时的推荐最小间距(本实现取 4px)
20 · 布局 Layout
20.1 用途
把一行内容按比例切成若干列(等分或按 12 栅格取值)。移动端屏幕窄,绝大多数场景是 2~4 等分;需要主次分栏时才用不等宽 —— 桌面端的「12 栅格 + 响应式断点」在移动端退化为「一档列数 × 可覆盖的列宽」。
20.2 结构(anatomy)
layout:行容器,display: flex+ 换行,承载列间距变量col:列,默认占满一行(flex-basis: 100%),由行级等分或列级 span 类决定实际宽度panel:列内容的可选包壳,提供内边距与最小高度,业务也可直接放自己的卡片gutter:列间距,四档(0 / 8 / 16 / 24),由行级类写到 CSS 变量上span:列级权重类(12 栅格取值),可覆盖行级等分
20.3 变体维度
gutter:0/8/16/24columns:0(不启用等分) /2/3/4align:start/center/end/stretchwrap:true(换行) /false(不换行、横向滚动)
20.4 状态
- default:常态
- 本组件无交互状态(纯排布,这是设计如此,不是缺失)
20.5 交互与触控
- 布局容器不绑点击,不设
cursor: pointer,热区始终由列内的业务元素提供 - 列内若是可点区域,其热区不小于 44×44(见 §19.5 与 §6.5)
gutter由 CSS 变量承载,同一行内所有列的宽度计算共用该变量,避免手工算宽wrap=false时容器横向滚动,纵向页面滚动不受影响- 列宽使用
flex-basis: calc(...),窄屏下不产生横向溢出(列内文本默认min-width: 0)
20.6 无障碍
- 布局是纯视觉分组:不添加
role、不添加aria-* - 不要为了布局把语义节点(列表 / 按钮)拆到不相邻的列里,否则读屏顺序会错乱
- 视觉顺序必须与 DOM 顺序一致(不用
order重排)
20.7 doNotInvent
- 响应式断点(移动端只有一档列数,不为大屏定义断点)
- 列的排序 / 拖拽
- 栅格嵌套的层级规则
20.8 unknowns
- 列内推荐的最大列数(本实现提供 2 / 3 / 4 三档)
- 列高等分(
stretch之外的等高策略)
21 · 链接 Link
21.1 用途
一段内联文字承载「跳转 / 打开下一级」或「触发一次轻量动作」。移动端与桌面端的差别在下划线策略:桌面端靠 hover 变色提示可点,触屏没有 hover,因此链接必须靠颜色常驻区分;默认不加下划线(正文里满屏下划线噪声大),正文段落内与条款页再加下划线。
21.2 结构(anatomy)
link:根元素,有href时是原生a,无href时是原生button(只回传事件)label:链接文字,单行不换行(超长由宿主截断)icon:可选尾部图标,继承链接颜色,与文字间距 4pxhref:跳转地址;禁用时不渲染该属性(否则仍可被打开)text:纯文字快捷入口,与默认插槽二选一
21.3 变体维度
tone:brand(默认) /default(继承父级文字色) /danger/successunderline:false/trueblock:false/true(撑满容器、整行可点)
21.4 状态
- default:常态
- active:按下时颜色加深 + 极浅底色(移动端没有 hover)
- disabled:置灰且不响应点击,同时移出 tab 序列
- visited:不区分(业务型链接不标注已访问,避免用户误判状态)
21.5 交互与触控
- 点击热区高度不小于 44px,文字可短但热区不缩水(内边距撑开)
- 按下反馈为颜色变化,不做位移缩放(避免长按抖动)
- 相邻链接之间至少留 8px 间距,防止误触(同时给出 16px 的更稳选择)
- 禁用链接点击不触发事件,也不跳转
21.6 无障碍
- 有
href用原生a(可聚焦、可长按复制、读屏报「链接」);无href用原生button - 禁用链接不能用
href,且要aria-disabled="true"+tabindex="-1"(移出 tab 序列) - 链接文字必须自解释,不要出现孤立的「点击这里」(读屏会脱离上下文朗读)
- 颜色不是唯一线索:正文段落内的链接必须带下划线或图标,避免色觉障碍用户无法识别
21.7 doNotInvent
- 外跳协议处理(
tel:/mailto:/ 唤起 App) - 已访问状态的样式
- 链接的埋点 / 统计
21.8 unknowns
- 一屏内链接的最大推荐数量
- 链接与相邻文字的推荐最小间距(同时给出 8px 与 16px 两档)
22 · 加载 Loading
22.1 用途
告诉用户「系统正在处理,请等」并占住当前位置。移动端与桌面端的差别在是否独占屏幕:桌面端加载指示多是区块内的一个转圈,移动端常需要 fullscreen 铺一层遮罩 —— 提交订单、支付这类不可中断的动作期间,必须挡住下方的重复点击。
22.2 结构(anatomy)
loading:根元素,内联形态(转圈 + 文案同行或上下排);fullscreen=true时它本身即遮罩层spinner:转圈,由 CSS 动画驱动(三端一致,不依赖图片或字体)text:可选文案,说明「在等什么」;为空时只有转圈panel:全屏形态下的卡片面板,承载指示器与文案,保证遮罩上的对比度可控mask:全屏形态的遮罩底色(取令牌,语义等同弹窗遮罩)
22.3 变体维度
size:small(16px) /default(20px) /large(28px)vertical:false(横行) /true(上下排布)fullscreen:false(区块内) /true(遮罩全屏)
22.4 状态
- loading:转圈持续旋转,
aria-busy="true"(默认语义) - open:
fullscreen=true且展开时可见并可截获手势 - done:加载结束后的过渡态(转圈停止),实际结果提示由宿主替换(本组件不自动消失)
- disabled:无独立禁用态 —— 加载中「不可操作」由遮罩承担,不是把控件置灰
22.5 交互与触控
- 加载组件本身不可点,也不抢焦点(不打断读屏正在读的内容)
fullscreen=true且open时遮罩截获手势,下方内容不可点;未展开时不截获- 转圈动画 800ms/圈(
--kole-m-loading-duration可覆盖),时长恒定不随尺寸变化 prefers-reduced-motion: reduce下停止旋转(改为静态环),避免前庭不适- 加载超过一次会话的合理时长时应由宿主提供取消入口,本组件不自己造取消按钮
22.6 无障碍
- 容器
role="status"+aria-live="polite"(状态变化被播报,且不打断当前朗读) - 加载中置
aria-busy="true";全屏遮罩未展开时置"false" - 转圈是装饰(
aria-hidden="true"),语义全部由文案与role="status"承担 - 文案要具体(「正在提交订单…」而不是「加载中」),读屏用户与视力用户获得同样的信息量
22.7 doNotInvent
- 加载耗时的进度百分比(本组件不假装知道进度)
- 超时后的自动提示 / 自动重试
- 多条加载的排队与合并
22.8 unknowns
- 全屏加载持续多久后应提示「可能需要更长时间」
- 文案的最大长度与换行策略
23 · 头像 Avatar
23.1 用途
用一张图或一两个字符代表一个主体(用户、企业、群组);移动端列表与详情页里大量出现,必须能单手扫读,因此尺寸只有三档、形状只有两种,且图片不可用时必须立刻退回文字,不能出现裂图。
23.2 结构(anatomy)
avatar:根元素,正方形圆角容器,尺寸由 size 决定image:图片,铺满容器并按形状裁切text:文字内容(姓名首字或简称),图片缺失时它就是主体fallback:兜底节点,图片加载失败后退回的形状(文字或图标)badge:可选右下角角标位,挂在线状态或未读数
23.3 变体维度
size:small(32px)/default(40px)/large(56px)shape:circle(圆形,用于人)/square(圆角方形,用于企业或群组)type:text(文字头像)/image(图片头像,失败退文字)
23.4 状态
- default:常态
- fallback:图片加载失败,退回文字或图标
- disabled:置灰(如成员已离职)
23.5 交互与触控
- 头像本身不是按钮;可点时必须由宿主包一层原生
button或a,热区不小于 44×44 - 图片
alt或根节点aria-label必填其一,读屏读「姓名 + 头像」 - 图片加载失败只触发一次
error事件,是否替换资源由宿主决定
23.6 无障碍
- 文字头像根节点
role="img"并带aria-label(内容为姓名) - 图片头像直接用
<img alt>,alt为空时视为装饰并对读屏隐藏 - 角标不改变头像的可访问名
23.7 doNotInvent
- 图片裁剪的 focal point(人脸居中)算法
- 角标位置随形状(圆 / 方)的微调规则
23.8 unknowns
- 文字头像的底色是否按名字散列取多色
- 姓名超过两个汉字时的截断规则
24 · 列表 List
24.1 用途
把一组同类信息按行排列,用于「设置项 / 订单 / 成员」这类需要扫读的场合;移动端一屏只有 6~8 行,因此每行信息层级必须收敛到「标题 + 可选的说明或值」,分组之间用标题与留白分隔,而不是靠边框。
24.2 结构(anatomy)
list:根元素,一个列表区块header:可选分组标题,位于列表之上item:列表项,一行承载「前缀 + 主区 + 后缀」prefix:可选前缀位(头像、图标、序号)body:主区,标题 + 可选副标题,两行都单行省略suffix:可选后缀位(值文字、标签、箭头、开关)footer:可选分组脚注,用于补充说明
24.3 变体维度
border:true(行间 1px 分隔线)/false(无分隔线,靠间距分组)size:default(行高 56px)/compact(行高 44px)divider:inset(分隔线缩进到行内容起点)/full(通栏分隔线)
24.4 状态
- default:常态
- active:行按下时整行背景变化
- disabled:置灰且不响应点击
- empty:列表为空时显示占位文案
24.5 交互与触控
- 可点行整行都是热区,
default行高不小于 56px、compact不小于 44px - 按下反馈是整行背景变化,不是只有文字变色
- 前缀位不参与点击判定(点图标等于点整行),后缀位里的独立控件(开关、按钮)要阻止事件冒泡,避免一次点击触发两个动作
- 列表滚动由宿主容器负责;本组件不接管滚动、不做虚拟列表
24.6 无障碍
- 容器
role="list",纯展示行role="listitem";可点行用原生button(原生语义优先于 listitem) - 分组标题用
aria-label或可见文本,读屏在进入分组时能读到 - 空列表用
aria-live="polite"播报占位文案
24.7 doNotInvent
- 虚拟滚动与无限加载的触发规则
- 行的拖拽排序与左滑操作(那是 SwipeCell 的职责)
24.8 unknowns
- 单行最多几列(前缀 + 主区 + 后缀之外的排布)
- 分组标题是否吸顶
25 · 折叠面板 Collapse
25.1 用途
把长内容按主题收起来,让用户先看到标题、按需展开某一段;移动端屏幕窄,展开后内容会顶走上下文,因此一次只展开一个(手风琴)是默认推荐形态,展开态必须明确到不靠颜色也能看出。
25.2 结构(anatomy)
collapse:根元素,一组面板的容器item:单个面板,含标题行与内容区header:标题行,整行可点,高度不小于 44pxarrow:标题行右侧箭头,展开时旋转 90°panel:内容区,展开时可见(收起时高度为 0 或hidden)content:内容区内的正文节点
25.3 变体维度
accordion:false(多面板可同时展开)/true(手风琴,同时只展开一个)bordered:true(面板之间有分隔线与外框)/false(无边框,靠留白分隔)
25.4 状态
- collapsed:收起(默认)
- expanded:展开
- disabled:标题行置灰且不响应
25.5 交互与触控
- 标题行整行都是热区,高度不小于 44px
- 视觉箭头转 90°(180ms 过渡),展开时
aria-expanded同步为true - 手风琴模式下展开新面板会收起当前展开项;
accordion=false时互不影响 - 内容区不做高度动画,直接切换
hidden(省电,读屏也不会读到中间态);减少动态偏好下箭头同样瞬时切换
25.6 无障碍
- 标题行用原生
button并带aria-expanded/aria-controls - 内容区与标题用
id/aria-controls建立关联,收起时用hidden属性隐藏(而不是只靠 CSS 高度) - 禁用项置
aria-disabled="true"且不可聚焦
25.7 doNotInvent
- 展开动画的高度换算公式(内容高度由浏览器决定)
- 嵌套折叠面板的层级样式
25.8 unknowns
- 默认是否展开第一项
- 标题行右侧是否允许放额外操作
26 · 进度条 Progress
26.1 用途
把一个过程的完成度可视化(上传、审核、额度耗尽);移动端的进度多数伴随文字出现,因此百分比文案与状态色是标配而非可选,且进度变化要能被读屏播报而不只是画出来。
26.2 结构(anatomy)
progress:根元素,承载轨道与文案track:轨道,未完成部分的底色bar:已完成部分,宽度由 percentage 决定ring:环形进度的圆环轨道(type=circle 时替代 track/bar)label:百分比文案,可置于条内、条右侧或环心status:状态图标位(成功 / 失败),非进行中时显示
26.3 变体维度
type:line(线形)/circle(环形)status:normal(进行中,品牌色)/success(成功)/error(失败)labelPlacement:inside(文案在条内)/right(条右侧)/center(环心,仅 circle)
26.4 状态
- default:进行中(0 < percentage < 100)
- complete:percentage = 100,文案显示 100%
- error:失败,进度停在断点并转为错误色
- paused:暂停,条体降透明度
26.5 交互与触控
- 进度条本身不可交互、不接收点击;需要取消时由宿主在旁边放按钮
- 数值变化用 CSS 宽度过渡(240ms,
--kole-m-progress-duration),不做无限循环动画(省电且不干扰读屏) - 无动画偏好(
prefers-reduced-motion)下直接跳到目标宽度
26.6 无障碍
- 根节点
role="progressbar"+aria-valuemin="0"/aria-valuemax="100"/aria-valuenow - 文案节点
aria-hidden="true",避免与aria-valuenow重复播报 - 不确定进度(无法给出百分比)用
aria-valuetext="进行中"表达
26.7 doNotInvent
- 环形进度的线宽与半径的自适应规则(由 size 决定,不做响应式推导)
- 进度到达 100% 后的自动隐藏时机
26.8 unknowns
- 百分比是否四舍五入到整数
- 环形进度是否支持渐变描边
27 · 输入框 Input
27.1 用途
在一行内收集单行文本(姓名、手机号、金额、验证码等);移动端与桌面端的关键差别是热区与字号:输入框整行占满、高度不小于 44px,字号不小于 16px,否则 iOS 聚焦时会自动放大页面;清除动作也必须在框内完成,因为触屏没有悬停的鼠标可以移开。
27.2 结构(anatomy)
field:字段容器,包裹输入框与下方错误提示(错误提示在框外,不挤占输入区)prefix:可选前缀,放单位或符号(如「¥」)control:原生input,占满剩余宽度,字号 16pxclear:可选清除按钮,有值且clearable=true时出现在右侧suffix:可选后缀,由默认插槽给出的自定义内容(单位、显示密码等)errorText:字段下方的错误提示文字
27.3 变体维度
size:default(高度 44px)/large(高度 52px)clearable:false(无清除按钮)/true(有值时右侧出现清除按钮)
27.4 状态
- default:常态
- focus:聚焦,边框变品牌色并带 2px 聚焦外发光
- error:错误,边框变错误色且
aria-invalid="true" - disabled:置灰且不可聚焦
27.5 交互与触控
- 输入框高度不小于 44px;整行可点(把
kole-m-input放在label里,点行即聚焦) - 清除按钮视觉是 16px 图标,热区外扩到 44px 最小触控边长
- 聚焦反馈是边框色 + 2px 外发光,150ms 过渡;不改变布局(不撑开高度)
- 有值时清除按钮才出现;清除后焦点留在输入框
- 键盘「完成」键触发 confirm 事件,值随 input 事件实时回传
27.6 无障碍
- 输入框用原生
input,名称由aria-label给出(占位文字不算标签) - 错误态用
aria-invalid="true",错误文案用aria-describedby关联 - 清除按钮是原生
button且带aria-label="清除" - 禁用态用原生
disabled,读屏会跳过
27.7 doNotInvent
- 输入内容的正则与业务校验(合法性判断在宿主)
inputmode之外的自定义软键盘行为(键盘类型由宿主按场景指定)- 自动填充与验证码自动读取的策略
27.8 unknowns
- 密码是否需要内置「显示/隐藏」开关
- 数字输入的千分位格式化时机
- 前缀里是否允许放图片/图标
28 · 搜索框 Search
28.1 用途
用关键词从长列表里取回一小段结果;移动端的搜索框几乎总是页面顶部的独立一行,输入即过滤(不等回车),并且要给出一个明确的退出动作 —— 取消,因为触屏没有 Esc 键,用户清空关键词后仍需一键回到列表。
28.2 结构(anatomy)
search:根元素,一行里放进「搜索框 + 取消动作」icon:框内左侧放大镜,纯装饰(不承载语义,读屏由 label 承担)control:原生input type="search",占满剩余宽度clear:可选清除按钮,有值且清除可用时出现在图标与取消之间cancel:可选取消动作,默认文案「取消」,由 showCancel 控制显隐
28.3 变体维度
round:true(胶囊形,页面顶部常用)/false(方角,嵌在卡片或工具栏里)showCancel:false(只有输入框)/true(右侧出现取消动作)
28.4 状态
- default:常态
- filled:有值(清除按钮出现)
- disabled:置灰且不可聚焦
28.5 交互与触控
- 搜索框高度不小于 44px,取消动作热区不小于 44px
- 输入即触发 input 事件(不等回车);键盘「搜索」键触发 search 事件
- 清除按钮只在有值时出现,点击清空并把焦点留在输入框
- 清除与取消都是 44px 热区;两者同时出现时先清除、再取消(自右向左层级递进)
28.6 无障碍
- 输入框用原生
input type="search"(iOS 键盘右下角键位变成「搜索」) - 搜索图标是装饰性的,置
aria-hidden="true",名称由aria-label给出 - 清除按钮是原生
button且带aria-label="清除";取消动作是原生button - 取消按钮不用图标代替文字(触屏上文字比图标更好点)
28.7 doNotInvent
- 搜索的防抖时值与接口节流策略(由宿主决定)
- 搜索历史的存储与展示
- 搜索结果的高亮规则
28.8 unknowns
- 取消文案是否允许替换(如「返回」)
- 是否需要自动聚焦并拉起键盘
- 语音/扫码等扩展入口是否放进框内
29 · 开关 Switch
29.1 用途
即时启停一项配置或业务状态(启用通知、公开数据、自动同步);移动端与桌面端的关键差别是热区:开关本体视觉只有 48×28,但整行(开关 + 文字)都是可点热区,行高不小于 44px,否则手指点不中;关态与开态也不能只靠颜色区分,必须同时看到滑块位移。
29.2 结构(anatomy)
switch:根元素,一行里放进「轨道 + 文字标签」,整行可点track:轨道,承载背景色与滑块位移的边界knob:滑块,关态靠左、开态靠右(位移是开/关的主要视觉信号)text:可选文字标签,说明这项开关控制什么control:可点的整行控件(原生button+role="switch"),承接键盘与读屏
29.3 变体维度
size:default(轨道 48×28)/small(轨道 40×22,用于紧凑表单)labelPlacement:right(文字在开关右侧,默认)/left(文字在左侧,值区右对齐时用)
29.4 状态
- off:关态(默认)
- on:开态,滑块位移 + 背景变品牌色,
aria-checked="true" - disabled:置灰且不可聚焦
29.5 交互与触控
- 整行(开关 + 文字)都是热区,行高不小于 44px;开关本体不可单独缩到 44px 以下
- 点击切换只需要一次触摸,不要求拖动滑块(拖动是桌面习惯,触屏误触率高)
- 切换动效是滑块位移 150ms 过渡;减少动态偏好下瞬时切换
- 关态与开态不能只靠颜色区分:滑块位置 +
aria-checked双通道 - 切换后立即触发 change 事件,不做二次确认(需要确认的场景由宿主先弹对话框)
29.6 无障碍
- 用
role="switch"+aria-checked="true|false",而不是role="checkbox"(读屏会播报「开关」) - 承载控件是原生
button,键盘可聚焦、空格/回车可切换,并有可见焦点环 - 文字标签在控件内部,读屏播报的名称就是标签本身;无标签时用
label属性补aria-label - 禁用态用原生
disabled,读屏会播报不可用
29.7 doNotInvent
- 二次确认弹窗与「切换失败回滚」的业务流程
- 三态开关(关 / 开 / 待定)的视觉表达
- 与表单一起提交时的隐藏字段(由宿主添加)
29.8 unknowns
- 开关本体是否允许小于 48×28(紧凑表单的下限)
- 文案与开关的间距是否跟随字号
- 加载态(切换请求进行中)如何表达
30 · 步进器 Stepper
30.1 用途
在一段有界区间里连续加减小整数(购买数量、份数、编号);移动端与桌面端的关键差别是加号与减号必须各自独立占一个 44px 见方的热区,且到边界时不是把按钮藏起来,而是置灰 —— 触屏上「按钮消失」会让用户以为界面坏了,而置灰能表达「到头了」。
30.2 结构(anatomy)
stepper:根元素,一行里放进「减号 + 值 + 加号」minus:减号按钮,到达min时置灰value:值区;editable=true时是可聚焦的数字输入框,否则是纯文本plus:加号按钮,到达max时置灰unit:可选单位文案,跟在值后面(如「件」),由默认插槽给出label:无障碍分组名称(role="group"+aria-label)
30.3 变体维度
size:default(控件高 44px)/small(控件高 36px,视觉更紧凑)round:false(方角)/true(全圆角,用于购物车等轻量场合)
30.4 状态
- default:常态
- disabled:整组置灰且不可聚焦
- 边界:值等于
min时减号置灰,等于max时加号置灰(按钮仍在原位,不隐藏)
30.5 交互与触控
- 加号与减号各自是 44px 见方的热区;
size=small视觉 36px 时用--kole-m-hit-slack把热区外扩回 44px - 单击步进一个
step,不响应长按连击(长按是桌面习惯,触屏上容易多跳) - 到达
min/max时对应按钮置disabled且置灰,点击不产生值与事件 - 值变化后立即触发 change 事件,不做防抖
editable=true时值区接受键盘直接输入;非法输入(非数字、越界)在失焦时回落到边界值
30.6 无障碍
- 根元素
role="group"+aria-label说明这组控件在调什么 - 减号 / 加号是原生
button,各自的aria-label是「减少」/「增加」(不用符号代替名称) - 值区在只读态置
role="spinbutton"并写aria-valuenow/aria-valuemin/aria-valuemax - 置灰的按钮用原生
disabled,读屏会播报不可用且键盘会跳过
30.7 doNotInvent
- 长按连击、惯性加速的时值曲线
- 小数与浮点精度(本组件只处理整数;金额请用输入框 + 数字键盘)
- 超出边界的提示文案(由宿主决定是否提示)
30.8 unknowns
- 值为 0 时是否自动隐藏整个步进器(购物车场景)
- 是否需要键盘上的上下方向键加减
- 单位文案是否随语言变化
31 · 多行文本框 Textarea
31.1 用途
收集可能超过一行的自由文本(备注、收货说明、退换原因);移动端与桌面端的关键差别是高度:文本框本身要够高(至少 3 行),因为触屏不能像桌面那样在输入过程中看到上下文;并且要给出实时字数反馈,超限时是「止写 + 报错」而不是静默截断。
31.2 结构(anatomy)
field:字段容器,包裹文本框、计数行与错误提示control:原生textarea,多行输入,行高不小于 1.5 倍字号counter:右下角字数计数(已输入/上限),maxlength>0 时出现errorText:字段下方的错误提示文字(与计数同行时计数不消失)label:无障碍名称(aria-label),textarea 的初始高度由 rows 决定
31.3 变体维度
size:default(最小高度 3 行)/large(最小高度 5 行,长文本场景)showCounter:false(不显示计数)/true(右下角显示「已输入/上限」)
31.4 状态
- default:常态
- focus:聚焦,边框变品牌色并带 2px 聚焦外发光
- error:错误,边框变错误色且
aria-invalid="true" - disabled:置灰且不可聚焦
- full:已达到
maxlength上限(计数变错误色,继续输入不再增加)
31.5 交互与触控
- 文本框最小高度 3 行(约 96px:3 × 24px 行高 + 上下 12px 内边距);只允许纵向伸缩(
resize: vertical),不允许横向拉宽破坏 375 宽的布局 - 计数随输入实时更新;达到上限时计数置错误色并停止接收新字符(原生
maxlength兜底,超限靠宿主提示) - 文本框整体可点即聚焦(外层不出可点装饰);错误提示与计数都在框外,不挤占输入区
- 聚焦反馈是边框色 + 2px 外发光,150ms 过渡;不改变高度
- 不使用自动增高(高度随内容跳动会让上下文错位),需要更长文本时用
size=large
31.6 无障碍
- 控件是原生
textarea,名称由aria-label给出(占位文字不算标签) - 错误态用
aria-invalid="true"+aria-describedby关联错误文案 - 计数是参考信息,用
aria-live="polite"播报(不要每敲一个字都播报,只在接近上限时提示) - 禁用态用原生
disabled,读屏会跳过
31.7 doNotInvent
- 自动增高(随内容撑高)的实现细节
- 富文本 / Markdown 的编辑与渲染
- 内容敏感词过滤与提交前的业务校验
31.8 unknowns
maxlength缺省时上限取多少(本实现默认 200)- 是否需要在接近上限时提前变色(本实现只在到达上限时变色)
- 计数是否包含空格与换行
32 · 表单 Form
32.1 用途
把一组字段(标签 + 控件 + 错误提示)组织成一次可提交的操作(下单、开票、认证);移动端与桌面端的关键差别是标签位置:窄屏放不下左右两列时标签默认置顶,只有需要一组字段纵向对齐时才退回到定宽左标签,且每个字段的错误提示必须贴在它自己的控件下方 —— 触屏上用户看不到「页头汇总错误」。
32.2 结构(anatomy)
form:根元素,包住全部字段与提交行item:单个字段,一列一个,字段之间用 1px 分隔线划分label:字段标签;required=true时前面带一个错误色星号star:必填星号,装饰性(aria-hidden),语义由控件的aria-required承担control:字段控件(原生input/textarea,或包一层的可点区域)errorText:字段级错误提示,落在该字段控件下方
32.3 变体维度
labelPosition:top(标签在控件上方,默认)/left(标签定宽 72px 与控件同行)borderless:false(字段之间有分隔线)/true(不画线,靠留白分组)
32.4 状态
- default:常态
- error:字段级错误,整项加
is-error,控件加aria-invalid="true",下方出现错误文案 - disabled:整表置灰且不可聚焦(子控件用原生
disabled)
32.5 交互与触控
- 控件高度不小于 44px;非输入型控件(选择器一类)整行都是热区,按下反馈是整行背景变化
- 错误提示出现在控件正下方,不挤占输入区,也不跨到标签列
- 失焦即校验(触屏上软键盘收起会触发
blur),提交时再全量校验一次 - 校验失败时不跳转、不滚动到页首,错误就落在出错的字段上
- 提交成功后写
data-submitted="true"作为可断言的提交凭证;星号与错误色不单独承载语义
32.6 无障碍
- 标签用
aria-labelledby关联到控件(标签不是占位符的替代品) - 必填用控件的
aria-required="true"与原生required双写;星号本身aria-hidden - 错误文案用
aria-describedby关联到控件,并带role="alert"播报 - 禁用态用原生
disabled,读屏会播报不可用且键盘会跳过
32.7 doNotInvent
- 字段内容的业务校验规则(正则、长度、合法性判断都在宿主,本组件只表达结构)
- 提交请求、失败重试与「提交中」的按钮态
- 字段的联动显隐与动态增删(由宿主决定渲染什么)
- 页首错误汇总条(移动端用字段级提示,不做汇总)
32.8 unknowns
- 左标签模式下标签列宽是否随字号变化(当前固定 72px)
- 提交行是否允许放两个并列动作(如「保存草稿 + 提交」)
- 只读(readonly)字段是否需要独立的视觉层,还是复用 disabled
33 · 单选框 Radio
33.1 用途
在一组互斥选项里选且只选一项(支付方式、配送时效、发票类型);移动端与桌面端的关键差别是选中态的表达:触屏没有鼠标悬停可以预告状态,选中必须同时有形状差异(圆点内圈实心)与 aria-checked,不能只靠颜色;且整行(圆点 + 文字)都是热区,行高不小于 44px。
33.2 结构(anatomy)
group:根元素,role="radiogroup"+aria-label说明这组在选什么item:单个选项,整行都是热区(原生button+role="radio")icon:圆点,未选中是空心环、选中是实心圆 + 内圈反色点label:选项文字,占满剩余宽度desc:可选说明行,跟在文字下方(如「需先完成企业认证」)state:选中态is-checked与aria-checked双写
33.3 变体维度
orientation:vertical(纵向排列,组内画分隔线)/horizontal(横向排列,靠间距分组)button:false(圆点 + 文字)/true(胶囊标签式,无圆点)
33.4 状态
- default:未选中(空心环)
- checked:选中(实心圆 + 内圈反色点,
aria-checked="true") - disabled:置灰且不可聚焦,读屏播报不可用
33.5 交互与触控
- 整行(圆点 + 文字)都是热区,行高不小于 44px;横向组里每项自身也保持这个边长
- 一次触摸即选中,不需要二次确认;选中后不能再点回空值(需要清空由宿主提供额外的「清除」动作)
- 互斥由组件负责:选中一项会立即清掉同组其它项的选中态,不留下两个选中
- 切换动效是圆点内圈 150ms 缩放;减少动态偏好下瞬时切换
- 选中后立即触发 change 事件,回传该项的值
33.6 无障碍
- 组用
role="radiogroup"+aria-label说明分组名称 - 每项是原生
button+role="radio"+aria-checked(读屏会播报「单选按钮,已选中/未选中」) - 圆点与内圈是纯装饰,对读屏隐藏(
aria-hidden),语义全靠role="radio" - 禁用项用原生
disabled并补aria-disabled="true"
33.7 doNotInvent
- 「取消选中」的回到空值交互(单选的语义就是必有一项)
- 横向组自动换行的列数策略(由宿主按文案长度决定,组件只负责 flex-wrap)
- 与表单一起提交时的隐藏字段(由宿主添加)
- 选项内容的异步加载与搜索过滤
33.8 unknowns
- 横向组超过一行时是否改成纵向(当前包裹后当行处理)
- 圆点尺寸是否随字号一起放大(当前固定 20px)
desc说明行是否允许两行以上(当前单行省略)
34 · 多选框 Checkbox
34.1 用途
在一组选项里同时选中任意多项(兴趣标签、订阅范围、筛选维度),并支持「全选」;移动端与桌面端的关键差别是半选态的表达:触屏上没有鼠标悬停预告状态,全选行在「部分选中」时必须显示出与「全选 / 全不选」都不同的第三态(横杠),且整行(方框 + 文字)都是热区,行高不小于 44px。
34.2 结构(anatomy)
group:根元素,role="group"+aria-label说明这组在选什么item:单个选项,整行都是热区(原生button+role="checkbox")icon:方框,未选中是空心框、选中是品牌底 + 反色勾号、半选是品牌底 + 横杠label:选项文字,占满剩余宽度desc:可选说明行,跟在文字下方(如「需先绑定手机号」)all:全选行,不存储自己的状态,由组内各项推导
34.3 变体维度
orientation:vertical(纵向排列,组内画分隔线)/horizontal(横向排列,靠间距分组)button:false(方框 + 文字)/true(胶囊标签式,无方框)selectAll:false(不显示全选行)/true(组首显示全选行)
34.4 状态
- default:未选中(空心框)
- checked:选中(品牌底 + 反色勾号,
aria-checked="true") - indeterminate:半选,只在全选行出现(
aria-checked="mixed") - disabled:置灰且不可聚焦,读屏播报不可用
34.5 交互与触控
- 整行(方框 + 文字)都是热区,行高不小于 44px;横向组里每项自身也保持这个边长
- 一次触摸即切换,各项互相独立;不限制同时选中的数量上限
- 点全选行:只要还有未选中项就全部选中,否则全部清空(不在「半选」上停留)
- 全选的选中态是推导值,不单独存储 —— 单项变化后立即重算,避免出现「全选已勾上但还有一项没选」
- 切换动效是勾号 150ms 缩放;减少动态偏好下瞬时切换
- 每次切换立即触发 change 事件,回传切换后的完整值数组
34.6 无障碍
- 组用
role="group"+aria-label说明分组名称 - 每项是原生
button+role="checkbox"+aria-checked(读屏会播报「复选框,已选中/未选中」) - 全选行的半选态用
aria-checked="mixed"(checkbox 角色允许的第三个值) - 方框与勾号是纯装饰,对读屏隐藏(
aria-hidden),语义全靠role="checkbox" - 禁用项用原生
disabled并补aria-disabled="true"
34.7 doNotInvent
- 「最多选 N 项」的数量上限与超出提示(业务规则在宿主)
- 分组嵌套(一组里再分组)的层级表达
- 与表单一起提交时的隐藏字段(由宿主添加)
- 选项内容的异步加载与「已选 N 项」的汇总条
34.8 unknowns
- 全选行是否显示「已选 2/5」这类计数
- 半选态在非全选行上的用例(如父级节点)
- 胶囊按钮式是否也需要禁用态的视觉层(当前复用同一条置灰规则)
35 · 滑动选择器 Slider
35.1 用途
在一个连续区间里用一次滑动选出数值(预算上限、价格区间、音量、亮度);移动端与桌面端的关键差别是热区:轨道可视高度只有 4px,但整条轨道所在的行必须是 44px 高的热区,且按下任意位置就要跳到该处 —— 触屏上要求用户精确抓住 20px 的滑块是点不中的。
35.2 结构(anatomy)
slider:根元素,一行里放进「轨道热区 + 数值」rail:轨道热区,44px 高的可聚焦区域,承载指针事件与锚点track:轨道本体(可视 4px),承载选中段与刻度的边界filled:选中段,从起点铺到滑块位置thumb:滑块,位置由当前值的百分比决定;拖动中放大作为「抓住了」的反馈mark:刻度点,仅marks=with-marks时出现(只标位置,不写文案)
35.3 变体维度
mode:single(单滑块)/range(双滑块,左值不越过右值)marks:with-marks(轨道上标出等分点)/none(不标刻度)showValue:true(右侧显示当前值)/false(不显示数值)
35.4 状态
- default:常态
- dragging:拖动中(滑块 20px → 24px,轨道加一层浅底)
- disabled:整条置灰且不响应,指针与键盘都不改值
35.5 交互与触控
- 轨道热区高 44px,按下任意位置即跳到该处,随后横向拖动连续改值;不要求抓住滑块
- 手势是「按下 + 拖动」一次连续动作,不响应长按或双击;横向分量优先,纵向滚动不被吞掉(
touch-action: pan-y) - 取值按
step吸附并夹在[min, max];mode=range时左值不越过右值、右值不越过左值 - 拖动期间滑块放大到 24px,松手后 150ms 回弹;减少动态偏好下瞬时切换
- 值变化立即触发 change 事件(连续回传),不做防抖;松手时才做业务提交
35.6 无障碍
- 单滑块:轨道热区是
role="slider"且可聚焦,写aria-valuemin/aria-valuemax/aria-valuenow/aria-label - 双滑块:外层是
role="group"+aria-label,每个滑块各自持有aria-valuenow(两条独立数值) - 禁用时写
aria-disabled="true",读屏会播报不可用 - 键盘:聚焦后左右方向键按
step步进(触屏之外的替代路径)
35.7 doNotInvent
- 数值输入框与滑块的双向绑定(宿主自行组合 Input,本组件只回传值)
- 刻度上的文案标注与区间高亮(只标位置,不写文案)
- 拖动结束后的「撤销 / 重置」流程
- 纵向滑块与圆形表盘式选择器
35.8 unknowns
- 双滑块的最小间距是否应当是 1 个 step(当前是),还是用户可配
- 刻度数量是否应由
step推导(当前按 5 等分固定标点) - 拖动中是否要把当前值放大显示在滑块上方(气泡提示)
36 · 评分 Rate
36.1 用途
让用户对一次体验给出星级评价(商品、物流、服务),或只读展示已有的平均分;移动端与桌面端的关键差别是没有悬停预览:桌面上鼠标划过就能预告「点下去会是几分」,触屏上这个预告不存在,所以填充比例(含半星)必须直接可读,且星形本体只有 24px、热区要补到 44px 高,否则手指点不准相邻的两颗星。
36.2 结构(anatomy)
rate:根元素,一行里放进「星组 + 数值文案」stars:星组,横向排列,只负责布局star:单颗星,可点区域(原生button),自身撑满 44px 高glyph:星形本体(视觉 24px),由灰底星 + 品牌色覆盖层叠成fill:覆盖层,宽度即该颗星的填充比例(100% 整星 / 50% 半星 / 0% 未选)text:数值文案,默认「N 分」,可由默认插槽替换
36.3 变体维度
size:default(星形 24px)/small(星形 18px,行内展示用)allowHalf:false(只能取整星)/true(允许半星)readonly:false(可评分)/true(只读展示已有评分)
36.4 状态
- default:未评分(全部灰底星)
- selected:已评分(填充比例 = 所选分值与满分的比例)
- disabled:置灰且不可聚焦,点击不改值
36.5 交互与触控
- 取值规则:点第 N 颗星取 N 分;
allowHalf=true时点第 N 颗星的左半取 N-0.5 分、右半取 N 分(半星只在开启该项时存在,默认整星) - 星形本体视觉 24px(
size=small时 18px),但每颗星的可点区域高度是 44px;横向不做死区,相邻星的边界就是两半分界 - 一次触摸即取值,不需要二次确认;不响应长按与拖动(拖动选分在触屏上容易滑错,规格未纳入)
- 已选中的部分用填充比例表达(半星就是左半填充),不能只靠颜色深浅区分
- 取值后立即触发 change 事件(回传 3.5 这类半星值也是合法输入)
readonly=true时不渲染任何可点区域(只读展示不抢键盘序列)
36.6 无障碍
- 可评分形态:根是
role="radiogroup"+aria-label,每颗星是原生button+role="radio"+aria-checked - 星形的名称是「N 星」(
aria-label),读屏播报的是分值而不是符号 - 只读形态:整组是一个
role="img"+aria-label(「评分 4.5 分(满分 5 分)」),组内星形aria-hidden - 禁用态用原生
disabled并补aria-disabled="true" - 键盘:聚焦后左右方向键步进(整星模式步长 1,半星模式步长 0.5)
36.7 doNotInvent
- 评分的业务含义映射(如「4 分以上算好评」)与统计口径
- 拖动选分与悬停预览(触屏没有悬停,拖动容易滑错)
- 评分理由 / 标签的联动采集(由宿主另行组合)
- 异步提交与失败回滚(宿主负责)
36.8 unknowns
- 分值是否允许与文案一一对应(如 1 分「很差」、5 分「很好」)
- 星形换用图标字体或 SVG 后填充比例的表达是否仍然一致
- 只读形态是否要显示评价条数(当前由宿主用插槽补)
37 · 排版 Typography
37.1 用途
把一段文字按信息层级(标题 / 正文 / 辅助 / 次要)成套地表达,并可选单行省略与一键复制。移动端与桌面端的关键差别是正文基准字号:桌面端正文 14px,移动端 16px(--kole-m-font-size-body),且层级只有四级 —— 手机屏幕上再细分「大标题 / 中标题 / 小标题」会让层级差异小于字号可辨阈值,用户只能看到「一堆差不多大的字」。
37.2 结构(anatomy)
typography:根元素,横向承载「文字 + 可选复制按钮」text:文字本体,层级、省略、换行都在它身上生效;默认插槽与textprop 二者取一(text优先)copy:复制按钮(原生button),仅copyable=true时出现;热区 44px,负外边距吸收不撑高行
37.3 变体维度
level:title(标题 17px / 600)/body(正文 16px / 400)/assist(辅助 14px / 次级色)/secondary(次要 11px / 三级色)ellipsis:false(按容器换行)/true(单行省略,省略号在行尾)copyable:false(纯展示)/true(右侧出现复制按钮)
37.4 状态
- default:常态
- copied:
is-copied—— 复制成功后的反馈(按钮文字换成「已复制」且转成功色),由宿主在写剪贴板后加上
37.5 交互与触控
- 组件本体是静态的:根与文字节点不带指针语义,也不进键盘序列(读屏只读文字)
- 唯一的交互元素是复制按钮:热区 44×44px(
--kole-m-touch-target),一次轻点触发copy事件 - 复制动作由宿主完成:组件不读系统剪贴板(Web 端
navigator.clipboard需要用户手势与安全上下文,小程序端完全没有这个 API,跨端无法统一),组件只回传事件并在宿主置copied=true后渲染反馈 ellipsis=true时只做单行省略:被截断的内容不提供展开入口,长内容应改用多行展示或详情页- 层级只表达信息次序,不做任何按数值/长度自动降级的推断
37.6 无障碍
- 根是普通容器,文字由读屏按文档流朗读;层级只改视觉,不改语义标签(是否用
h1/p由宿主决定,组件不替业务决定文档大纲) - 省略形态下读屏仍能读到完整文字(
text-overflow: ellipsis只截视觉,不改可访问名) - 复制按钮是原生
button且带aria-label,名称随状态在「复制」与「已复制」之间切换 - 复制结果是异步确认的视觉反馈,因此不额外加
aria-live(避免与页面其它播报抢读)
37.7 doNotInvent
- 富文本与 Markdown 渲染(换行、加粗、链接、代码块一律由宿主负责)
- 多行省略(
line-clamp)与「展开全文」交互 - 字号缩放 / 用户字号偏好档位
- 复制失败的兜底提示(宿主自行组合轻提示)
37.8 unknowns
secondary层级是否应使用 11px(当前取--kole-m-font-size-caption)而不是 12px- 复制按钮在「已复制」态停留多久(当前不自动回退,由宿主控制)
- 省略态是否需要在长按气泡里显示全文
38 · 分段器 Segmented
38.1 用途
在 25 个互斥选项里选一个,并让「当前选的是哪个」一眼可见(订单状态、时间范围、列表/网格视图切换)。移动端与桌面端的关键差别是没有悬停:桌面端用户把鼠标移到未选项上就能预览「这里可以点」,触屏上这个预告不存在 —— 所以选中项必须靠形态(白底滑块 + 阴影)而非色相区分,且每一项都要占满 44px 高,否则 25 个选项挤在一行时手指点不准。
38.2 结构(anatomy)
segmented:容器,浅底 + 内边距,横向排列选项;role="radiogroup"并带aria-label说明这组在选什么item:单个选项,原生button+role="radio"(整项可点、可键盘聚焦),data-value携带取值label:选项文字(可含装饰图标,图标aria-hidden由文字承担语义)
38.3 变体维度
size:default(44px 高)/small(32px 高,卡片内次级筛选用)block:false(宽度随内容)/true(占满容器且各项等分)
38.4 状态
- default:未选中(透明底 + 次级字色)
- selected:选中(
is-active—— 卡片底滑块 + 品牌字色 +aria-checked="true") - disabled:整段置灰、不响应点击;单个选项也可单独禁用
38.5 交互与触控
- 选项切换是一次轻点:点击后该项
is-active、同组其它项复位;不响应长按、双击与拖动(横滑切换属于标签栏手势,不在本组件) - 选中态是视觉与属性的双向同步:除类名外必须同时更新
aria-checked,只改颜色不改属性会被无障碍判为缺口 - 每项热区高 ≥ 44px(
size=small时视觉 32px,但触摸命中区仍按 44px 计,纵向不留死区) - 选中项再次点击不重复触发
change(值未变不发事件,避免宿主收到同值事件后做无意义的重渲染) - 受控:组件不存值,只回传目标值
change;宿主不采纳时视觉不变化 - 切换动效 120ms(
--kole-duration-fast),prefers-reduced-motion下瞬时切换
38.6 无障碍
- 容器
role="radiogroup"+aria-label;每个选项原生button+role="radio"+aria-checked - 选项名称由文字承担;装饰性图标必须
aria-hidden="true" - 禁用时容器补
aria-disabled="true",选项用原生disabled(读屏播报不可用,且不进 Tab 序列) - 键盘:Tab 进入分组,左右方向键在选项间移动并选中(原生 radio 组的键盘约定)
38.7 doNotInvent
- 多选(同时选中多个)—— 需要多选时改用标签组或多选框
- 选项的横向滚动、换行与「更多」折叠(超过 5 个应换组件)
- 选中项的下划线滑块动画(那是标签栏的视觉语言,不是分段器)
- 选项禁用条件与业务权限的判断
38.8 unknowns
- 选项数量上限是否应硬约束在 5 个(当前只写建议,不做运行时拦截)
size=small的命中区是否需要在纵向自动补到 44px(当前靠--kole-m-hit-slack思路,未在样式中强制)- 是否需要「滑动经过即选中」(当前只认轻点,滑动不选中)
39 · 吸顶容器 Sticky
39.1 用途
让一段内容在滚动时贴住滚动容器的边缘保持可见(列表标题、分组、购物车合计条)。移动端与桌面端的关键差别是滚动惯性:桌面端滚动是离散的滚轮步进,用「监听 scroll + position: fixed」的 JS 方案看不出问题;触屏上滚动是带惯性的连续位移,fixed 方案会在惯性阶段出现一帧的抖动与跳位,且元素脱离文档流后原位置塌陷、必须手动补一个占位元素 —— 所以这里用 CSS 原生的 position: sticky,贴合由浏览器在合成层完成,不监听滚动。
39.2 结构(anatomy)
sticky:根元素,就是滚动内容流里的那一行(sticky 不脱离文档流,因此不需要占位元素)title:标题文字,单行省略,占满剩余宽度extra:右侧附加说明(数量、合计等),可选action:右侧动作按钮(原生button,热区 44px),可选
39.3 变体维度
position:top(贴顶,最常用)/bottom(贴底,合计条一类)safeArea:false(只用偏移量)/true(偏移量再叠加刘海或底部横条安全区)shadow:false(恒定无投影)/true(仅吸顶后才有投影,用于区分「贴住了」与「还在流里」)
39.4 状态
- default:未吸顶,正常参与文档流
- stuck:
is-stuck—— 已贴合边缘(加下边框 / 投影)。判定由宿主负责:贴合时机只有浏览器知道,组件不监听滚动
39.5 交互与触控
- 贴合位置由组件级变量
--kole-m-sticky-offset决定,默认等于导航栏高度;业务侧覆盖它即可适配自有导航(贴底用法通常覆盖为0px) safeArea=true时偏移叠加--kole-m-safe-top/--kole-m-safe-bottom:支持env()的浏览器拿到真实刘海高度,不支持的拿到0px(不会因非法值整条声明被丢弃)- 组件本身不监听滚动:
is-stuck由宿主按滚动位置切换。原因有两条 —— ① 贴合判定与滚动容器强绑定,组件无法知道自己在哪个容器里;② 逐帧读写滚动位置在移动端会挤占合成线程,原生 sticky 已经把这件事交给浏览器 - 触屏热区:整条高度 ≥ 44px;右侧动作按钮自身撑满 44px,负外边距吸收不撑高栏体
position=bottom时吸顶后的描边换到上边(视觉上仍是「靠内容那一侧」的分隔)- 不响应长按、双击与横滑:吸顶条不是可拖拽元素(拖拽排序不在本组件范围内)
39.6 无障碍
- 根是普通容器,标题文字正常参与读屏朗读;
is-stuck是纯视觉增强,不添加任何aria-*(吸顶不是状态变化,播报它只会打断用户) - 右侧动作是原生
button,名称由可见文字承担;装饰性图形必须aria-hidden="true" - 吸顶不改变文档顺序:读屏与键盘的遍历顺序与未吸顶时完全一致(这也是不用
fixed的另一个理由 ——fixed会它把挪出正常流,键盘聚焦时的滚动定位会跳) prefers-reduced-motion下不引入任何过渡(组件本身不加动效,此处显式声明避免宿主样式穿透)
39.7 doNotInvent
- 吸顶触发的位移/缩放动画(如标题从大字缩成小字)—— 规格只定义了贴合,没有定义形态演变
- 多个吸顶条的层叠顺序与相互推挤(层叠上下文规则由宿主决定)
- 进入/离开视口时的埋点事件与曝光统计
- 拖拽排序与吸附
39.8 unknowns
- 吸顶判定是否应由组件内部提供一个可选的滚动监听辅助(当前完全交给宿主)
- 是否需要在吸顶时自动隐藏相邻内容(当前不做,靠宿主布局)
- 贴底形态在内容不足一屏时是否应始终贴底(当前 sticky 语义下会贴着容器底,未做额外分支)
40 · 遮罩层 Overlay
40.1 用途
浮层的基座:在内容之上盖一层半透明遮罩,让下层内容退到背后(对话框、抽屉、图片预览、卡片加载态)。它与弹出层的分工是:弹出层自带方向位移与开合动画、是「能独立使用的完整浮层」;遮罩层只提供「变暗 + 拦手势 + 承载任意内容」三件事,属于被别的浮层复用的底座。移动端与桌面端的关键差别是手势拦截面:触屏上遮罩必须真的吃掉触摸事件(否则惯性滚动会从遮罩底下穿过去,把下层页面滚走),而桌面端只需要处理鼠标点击。
40.2 结构(anatomy)
overlay:根元素,定位容器与开合开关;role="presentation",自身不承担语义scrim:遮罩面(原生button),aria-hidden="true"且tabindex="-1"—— 可点但不进键盘序列content:内容容器(默认插槽落点),语义由宿主决定(对话框给role="dialog"、面板给role="region")
40.3 变体维度
tone:default(普通遮罩)/strong(浓遮罩,用于需要专注的确认)/blur(叠加背景模糊,用于图片预览)contained:false(固定全屏)/true(绝对定位填充最近的定位祖先,做卡内局部遮罩)
40.4 状态
- closed:收起(
opacity: 0+pointer-events: none,视觉与手势同时让开) - open:展开(
is-open;pointer-events: auto拦下所有手势)
40.5 交互与触控
- 一次轻点遮罩即关闭;
closeOnMask=false时不关闭(填到一半的表单不该因为一次误触丢数据) - 遮罩关闭的键盘路径不落在遮罩上:遮罩显式
tabindex="-1",键盘用户靠内容里的关闭按钮(与宿主实现的 Esc)关闭 —— 让 Tab 停在遮罩上会让读屏读到一个没有名称的元素 lockScroll=true时宿主应锁住下层滚动(组件在根上写data-lock-scroll作为标记,实际的overflow: hidden由宿主执行:组件不该直接改document.body.style,那会污染宿主状态并在多层浮层叠加时互相踩踏)- 开合动效 240ms(
--kole-m-duration-slide),prefers-reduced-motion: reduce下瞬时切换 contained=true时要求宿主祖先链上存在定位元素(position: relative一类),否则会向上找到视口
40.6 无障碍
- 遮罩面
aria-hidden="true"(纯装饰)且tabindex="-1"(不进键盘序列) - 根
role="presentation":容器本身不产生语义,避免读屏把它当成一个空的分组 - 内容语义完全由宿主提供:焦点陷阱、
aria-modal、初始焦点与关闭后焦点归还都属于宿主职责(本组件是基座,不替业务决定这些策略) - 遮罩关闭不依赖颜色:开合只改透明度且有 240ms 过渡,减少动态偏好下瞬时切换
40.7 doNotInvent
- 焦点陷阱(focus trap)与初始焦点策略
- 多层遮罩的层叠顺序管理
- 手势下滑关闭与拖拽阻尼
- 滚动锁定的实现细节(组件只写标记,改 DOM 由宿主做)
40.8 unknowns
blur档在低端机上的性能开销是否可接受(未做降级探测)contained=true时是否应自动为宿主补position: relative(当前要求宿主自己保证)- 是否要支持「点遮罩不关但双击关」这类折中策略
41 · 弹出气泡 Popover
41.1 用途
在某个元素的旁边弹出一小块说明或轻量操作(运费规则、字段解释、更多操作),说完就收。移动端与桌面端的关键差别是触发方式与关闭路径:桌面端靠 hover 弹出、移开即收;触屏没有悬停,只能点击展开,而展开后「怎么收起来」变成了真问题 —— 桌面上移开鼠标就收了,触屏必须显式给一条关闭路径(点击气泡之外)。这也是它与轻提示 / 遮罩层的根本区别:气泡是相对某个触发元素定位的,不是铺满视口的浮层。
41.2 结构(anatomy)
popover:根元素,position: relative的包一层,气泡在其中绝对定位(因此永远贴着自己的触发器)trigger:触发器(原生button),热区 ≥44px,写aria-expanded与aria-haspopuppanel:气泡面板,按placement贴着触发器的某一侧;role="dialog"并带aria-labeltitle/text:标题与正文,可选- 箭头:
arrow=true时由纯 CSS 三角(border 拼出,无图片、无 hex)指向触发器
41.3 变体维度
placement:top/bottom/left/right(气泡相对触发器出现在哪一侧)arrow:false(不带三角,小屏空间紧张时用)/true(三角指向触发器)
(closeOnOutside 是行为开关而不是形态维度,见 §41.5:它只改变关闭路径,不改变任何视觉。)
41.4 状态
- closed:收起(
opacity: 0+visibility: hidden+pointer-events: none,不可见也不可点) - open:展开(
is-open;触发器同步aria-expanded="true"并转为品牌色描边)
41.5 交互与触控
- 一次轻点触发器展开 / 收起,同一个按钮负责开与关(触屏没有「移开鼠标」这条隐式关闭路径)
- 点击外部关闭:落点不在「气泡或触发器」之内时收起。气泡没有遮罩可依赖(有遮罩就成了弹出层),判定靠监听文档级
pointerdown;uni-app 端没有document,改用铺满视口的透明捕获层(层级低于面板) - 不响应长按、双击与拖动;气泡自身不消费纵向滚动 —— 内容超长应改用弹出层,不在气泡里做内部滚动
- 气泡与触发器之间留
--kole-m-popover-gap(默认 8px)的间隙,避免气泡盖住触发器的按下反馈 - 开合动效 120ms(
--kole-duration-fast),减少动态偏好下瞬时切换 - 边缘空间不足时的翻转(flip)由宿主决定:把
placement换成对侧即可,组件不做自动测量
41.6 无障碍
- 触发器是原生
button,带aria-expanded(读屏能播报「已展开 / 已折叠」)与aria-haspopup - 面板
role="dialog"+aria-label:名称取title,为空时退回触发器文字,保证读屏不会读到一个匿名对话框 - 弹出时不移动焦点(气泡是补充说明而非中断式浮层,抢焦点会让用户丢失当前输入位置);键盘用户用 Tab 进入气泡内容,用 Esc 或再点一次触发器收起
- 气泡不因展开而隐藏任何内容:关闭后触发器仍在原地可再次打开
41.7 doNotInvent
- 基于可用空间的自动翻转与自动方位选择(
placement由宿主决定) - 悬停触发(触屏没有悬停;桌面端若需要,由宿主包装)
- 气泡内的表单校验与提交流程
- 气泡之间的互斥开关(哪个开着由宿主管理)
41.8 unknowns
- 是否需要在气泡贴近视口边缘时自动夹在边界内(当前只做静态定位)
role="dialog"对纯文字说明类气泡是否过重(当前统一用 dialog + aria-label)- 点击外部关闭是否需要区分「点了另一个气泡」的情况(当前点另一个气泡会同时收起前一个)
42 · 消息通知 Message
42.1 用途
在页面顶部给一条不打断操作的结果提示(提交成功、网络异常、库存告警、操作回执)。移动端与桌面端的关键差别是不阻断:桌面端顶部提示常做成整条横幅、推进页面布局;触屏上页面本身就是有限的可视区域,横幅会把内容挤下去并引发重排,所以这条消息浮在内容之上(position: fixed)且不吃手势(层自身 pointer-events: none),用户读完继续操作,页面不跳。它与轻提示 Toast 的分工是:Toast 占据视口中央、用于「结果就是你此刻唯一关心的事」;Message 贴在顶部一条、用于「结果要告诉你,但不该拦着你」。
42.2 结构(anatomy)
message:消息层根元素,顶部固定的纵向列表容器;pointer-events: none让下方页面照常可点item:单条消息,一行「图标 + 文字(+ 可选关闭)」,带role="status"与aria-live="polite"icon:语气图标(装饰),aria-hidden="true",语义由文字承担text:消息文字,超长换行,不截断close:可选的关闭按钮(原生button,热区 44px)
42.3 变体维度
tone:info(信息)/success(成功)/warning(警告)/error(错误)—— 图标字形与侧边描边同族,文字保持正文色closable:false(读完自己消失)/true(右侧出现关闭按钮)
42.4 状态
- closed:收起(
opacity: 0+ 上移 100%,不可见) - open:展开(
is-open,滑下淡入 240ms)
42.5 交互与触控
- 两种用法,API 只暴露组件形态:
- 组件形态(本组件提供的接口):受控
open+ 内容 props;duration到点回传close,由宿主决定是否收起 - 命令式调用(宿主侧组装):宿主维护一个消息数组与定时器,把
open/tone/text逐个喂给组件实例 —— 命令式 API 需要单例容器、跨端定时器与「销毁后仍在计时的定时器」治理,属宿主或框架层职责,组件不提供全局方法(规格 §42.7)
- 组件形态(本组件提供的接口):受控
- 消息层不吃手势(根
pointer-events: none),只有关闭按钮自己接收手势(pointer-events: auto)——因此顶部有消息时,下方页面仍可正常点击与滚动 - 自动消失时长由宿主决定:组件没有默认时长之外的隐式行为,
duration=0表示不自动关闭(需要用户读完的长文案) - 入场从上滑下并淡入 240ms(
--kole-m-duration-slide),减少动态偏好下瞬时切换 - 多条并列时纵向排列,新的追加在下方;同屏条数上限与超出后的合并策略由宿主决定(规格 §42.7)
42.6 无障碍
- 每条消息
role="status"+aria-live="polite":读屏朗读一次,不打断用户当前操作(不用assertive,那会打断朗读) - 语气图标
aria-hidden="true",语义全部由文字承担(颜色不是唯一的信息通道:图标字形与描边同时变化) - 关闭按钮是原生
button并带aria-label(「关闭消息」),可用 Tab 聚焦、Enter 触发 - 消息不抢焦点:出现时不移动焦点,用户正在输入的内容不受影响
42.7 doNotInvent
- 自动关闭的默认时长与「超时后是否保留」的策略
- 多条消息的排队、合并、去重与同屏上限
- 命令式全局方法(
Message.success()这类)与其单例容器 - 消息内的操作按钮与跳转链接(需要动作时请用通知栏或对话框)
42.8 unknowns
tone=warning与error是否需要不同的停留时长(当前统一由宿主传duration)- 顶部多条同时出现时是否该限制为最多两条(当前不限,由宿主控制)
- 是否需要在消息层上提供「点整条跳详情」的交互(当前只有可选的关闭按钮可点)
43 · 选择器 Picker
43.1 用途
从一组有限且已知的选项里选出一项或几项(城市、分值、时间),是「表单里那个下拉框」在触屏上的形态;移动端与桌面端的关键差别是没有悬停、也没有空格去展开:下拉框在手机上只能变成从底部升起的滚轮浮层,手指点选即高亮,并且必须有明确的「确定 / 取消」来收口 —— 桌上点一下就走、触屏上误触代价大,用户需要一次反悔的机会。可绑定到 Popup 的底部形态(position: bottom),但本组件自带遮罩与面板,不要在弹出层里再套一层。
43.2 结构(anatomy)
mask:遮罩,点击关闭(closeOnMask=false时不关)picker:底部浮层面板,role="dialog"+aria-modal="true"picker__header:取消 / 标题 / 确定三格,标题元素由aria-labelledby指向picker__columns:列容器,mode=single一列、mode=multiple多列等分picker__column:单列,role="listbox"+aria-label说明这列在选什么,可滚动picker__option:单个选项,role="option"+aria-selected,行高不小于 44px
43.3 变体维度
mode:single(单列滚轮)/multiple(多列滚轮,各列独立选中;是否联动由宿主的选项决定)round:false(直角)/true(靠内容一侧切圆角)
43.4 状态
- default:常态
- open:浮层展开(遮罩可点、面板滑入)
- closed:收起态(遮罩
pointer-events: none,页面可正常滚动与点击) - selected:当前选中项,
is-selected与aria-selected="true"同步表达 - disabled:整块或单个选项置灰且不响应,写
aria-disabled="true"(整块禁用用状态类is-disabled)
43.5 交互与触控
- 遮罩点击关闭;
closeOnMask=false时不关闭 - 选项行高不小于 44px,滚动容器
-webkit-overflow-scrolling: touch - 点击选项只改本列高亮(同列其余项取消高亮),不改宿主的值;确认时才提交
- 确定 / 取消按钮热区不小于 44px
- 取消防返回:点取消或遮罩丢弃本次点选,宿主侧的值回到打开前的状态
43.6 无障碍
- 浮层
role="dialog"+aria-modal="true",标题元素 id 由aria-labelledby指向 - 每列
role="listbox"+aria-label,选项role="option"+aria-selected - 遮罩
aria-hidden="true"(纯装饰,读屏不播报) - 选中值以文本呈现在标题里(不依赖视觉滚动位置),收起态写
aria-hidden="true" - 禁用项写
aria-disabled="true",读屏播报不可用
43.7 doNotInvent
- 选项数据源与联动规则(由宿主传入
columns,本组件不发明城市库或级联关系) - 滚轮惯性 / 吸附动画的物理参数
- 搜索过滤与键盘输入定位(那是 Input / Search 的职责)
- 多选(一次选多个值)——本组件是「多列各选一项」,不是「一列选多项」
43.8 unknowns
- 面板最大高度是否应随列数增长(当前固定
max-height一列 200px) - 是否要支持「不选」的空值项
- 列数上限(当前实现不限制,但三列以上在 375px 宽度下每列会很窄)
44 · 级联选择器 Cascader
44.1 用途
在有层级关系的选项里逐级选到末级(省 → 市 → 区、品类 → 子品类 → SKU),移动端与桌面端的最大差别是没有横向空间:桌面上三列并排一眼就能看到全路径,375px 宽的手机上并排三列每列只剩 100px 出头,文字全部折行不可读。因此移动端一次只展示当前一层,已选路径收进上方路径条,靠路径条回退而不是靠「上一级」按钮 —— 那会多一次点击。
44.2 结构(anatomy)
cascader:根元素,mode=panel时内嵌在页面里,mode=popup时是底部浮层mask:遮罩(仅mode=popup),点击关闭cascader__header:取消 / 标题 / 确定三格(仅mode=popup)cascader__path:路径条,按已选深度渲染;除末位外都可点,点了回退到该级cascader__panel:选项区,role="listbox"+aria-label说明当前在选第几级、选什么cascader__option:单个选项,role="option"+aria-selected;有下级显示›,叶子显示✓
44.3 变体维度
mode:panel(内嵌在页面里)/popup(底部浮层,自带遮罩与确定取消)showPath:true(显示路径条,可回退)/false(不显示,层级少的场景省一行高度)round:false(直角)/true(浮层靠内容一侧切圆角)
44.4 状态
- default:常态(停在根级)
- selected:当前路径上的项,
is-selected与aria-selected="true"同步 - open / closed:仅
mode=popup,浮层展开 / 收起(收起时遮罩pointer-events: none) - disabled:整块或单个结点置灰且不响应,
aria-disabled="true" - empty:当前级没有可选项时显示占位文案
44.5 交互与触控
- 点某一级后下一级选项随之变化:面板始终只渲染当前一层,选择即下钻
- 点路径条里的上级可回退:回退到该级并重新展示它的下一级;末位是「你在这里」的锚点,不可点
- 级数不写死:路径条按已选深度渲染,两级与四级用同一份实现
- 点到叶子结点后再点同级另一项,会截断更深的层级(改选不会留下旧的深层残留)
- 选项行高与路径条各项热区均不小于 44px;选项区滚动容器
-webkit-overflow-scrolling: touch
44.6 无障碍
mode=panel根元素role="group"+aria-label;mode=popup根元素role="dialog"+aria-modal="true"- 选项区
role="listbox",aria-label随层级变化(如「级联选项 · 城市」),读屏能播报当前在第几级 - 选项
role="option"+aria-selected;禁用项写aria-disabled="true" - 路径条末位写
aria-current="true",读屏播报「当前项」 - 路径条每一项是原生
button(键盘可达),末位用disabled让键盘跳过
44.7 doNotInvent
- 层级数据源(由宿主传入
options树,本组件不发明省市区库) - 搜索定位某级选项(那需要把整棵树拍平,是独立的检索组件)
- 异步逐级加载的占位与重试流程
- 多选(一次选多条路径)
44.8 unknowns
- 路径很长时路径条是折行还是横向滚动(当前折行,四级以上会占两行)
- 叶子被选中后是否要自动收起浮层(当前不自动收,等宿主决定)
- 是否要保留「上一级」按钮作为路径条之外的第二种回退入口
45 · 颜色选择器 ColorPicker
45.1 用途
从一组预设色里挑一个颜色(主题色、标签色、看板分类色),或在预设之外补一个自定义色值;移动端与桌面端的关键差别是没有取色器画布的位置,也没有精确拖拽的精度:375px 宽下色相环只有一百多像素、手指覆盖几十像素,拖出来的色值几乎不可复现。因此移动端默认只给预设色板——色值是离散的、可预期的,选中与否一眼可辨;需要自由取色时退化为「输入色值」而不是「拖色相」。
45.2 结构(anatomy)
colorpicker:根元素,承载当前色与色板colorpicker__current:当前色显示,由「圆点 + 色值文字」组成colorpicker__dot:当前色圆点,背景取--kole-m-colorpicker-current(色值是数据,由宿主以行内自定义属性传入)colorpicker__value:色值文字(等宽字),读屏靠aria-label而不是色块本身colorpicker__swatches:色板,role="radiogroup"+aria-label,定列数网格colorpicker__swatch:单个色块,role="radio"+aria-checked,背景取--kole-m-colorpicker-swatchcolorpicker__custom:自定义色值输入区(仅mode=custom),由宿主以插槽填入
45.3 变体维度
mode:swatch(只有预设色板)/custom(色板之后追加自定义色值输入)round:false(方形色块)/true(圆形,主题选择器常用)showValue:true(显示色值文字)/false(只留色块,空间紧张时用)columns:色板每行列数(默认 6,通过--kole-m-colorpicker-columns覆盖)
45.4 状态
- default:常态
- selected:当前选中色块,
is-selected与aria-checked="true"同步;视觉是外环 + 轻微放大,不是换色 —— 浅色块(白、浅黄)上换色根本看不出来 - disabled:整块置灰且不响应;色块写
aria-disabled="true"并用原生disabled让键盘跳过
45.5 交互与触控
- 点色块即选中并回传该色;同组互斥(选新的自动取消旧的)
- 色块视觉 ≥32px、热区按网格列宽铺满,点空白处不响应
- 选中反馈是外环 +
scale(1.08),变换用--kole-duration-fast;减少动态偏好下不放大 mode=custom时色值输入只在确认(点「应用」或回车)后生效,输入过程中不回传- 色值非法时输入框置红并给出错误文案,不自动纠正(不发明「就近取色」这类行为)
45.6 无障碍
- 色板
role="radiogroup"+aria-label说明这组在选什么(如「主题色」) - 每个色块
role="radio"+aria-checked,aria-label必须是色值本身(如#2F54EB)—— 色块没有文字,读屏只能靠 aria-label - 当前色以等宽文字重复一遍:颜色不作为唯一信息通道(色觉障碍用户读得到色值)
- 禁用块写
aria-disabled="true",读屏播报不可用
45.7 doNotInvent
- 色相环 / 明度滑条的取色画布(移动端精度不足,本组件不提供)
- 颜色空间转换(RGB / HSL / HSV 互转)与色值自动纠正(只接受
#RRGGBB) - 主题派生(由品牌色自动生成 hover / active / 浅底等衍生色阶)
- 取色器(吸管)与屏幕取色
45.8 unknowns
- 色板上限(当前不限制数量,但 40 个以上在小屏上会变成一片色噪)
- 自定义色值的格式是否要支持
rgb()/hsl()书写 - 是否需要「最近使用」一行动态色块
46 · 上传 Upload
46.1 用途
把手机里的文件交给服务端(实名认证的身份证照、报销的发票、工单的附件)。移动端与桌面端的关键差别是没有拖拽:桌面上可以「把文件拖进虚线框」,手机上既没有 hover 也没有拖放,所以触发按钮是唯一入口,虚线框只能表达「这里可以放东西」而不能作为交互方式;另一个差别是相机——很多上传场景其实期望的是「拍一张」,宿主可以用同一个触发器同时给出「拍照」与「从相册选择」,但那是宿主的编排,不是本组件的职责。
46.2 结构(anatomy)
upload:根元素,纵向排列「标题行 + 触发器 + 文件列表」upload__header:标题与计数(已选 N / 上限 M)upload__trigger:选择触发器,点击调起系统文件选择器;虚线框是variant=dashed,实心按钮是variant=buttonupload__list/upload__item:文件列表与单行,行高不小于 44pxupload__thumb:缩略图位(图片用背景图,其他类型放扩展名文字块)upload__body/upload__name/upload__meta:文件名与「进度条 + 状态文案」upload__track/upload__bar:进度条轨道与进度段,宽度取--kole-m-upload-percentupload__actions:行内动作(重试 / 删除),各自 44px 热区
46.3 变体维度
variant:dashed(虚线框,页面级上传区)/button(实心按钮,列表内嵌的「+ 添加」)maxCount:文件数上限(到达上限后触发器变成提示行,不再可点)
46.4 状态
- pending:待上传(进度条不占宽,文案「待上传」)
- uploading:上传中(品牌色进度条,文案给百分比;
role="progressbar"+aria-valuenow) - success:成功(满格 + 成功色 +「已上传」)
- error:失败(满格 + 错误色 + 失败原因,行内出现「重试」)
- empty:还没有任何文件时的说明文案
- disabled:整块置灰(状态类
is-disabled,触发器与删除按钮都不可用);到maxCount后触发器变提示行
46.5 交互与触控
- 点触发器调起系统文件选择器(本组件不发请求,选完由宿主拿文件并自行上传)
- 删除按钮自己 44px 热区,点一次移除该项;删除进行中(
uploading)时文案是「取消」 - 失败行提供「重试」,重试由宿主重新发起,组件只回传下标
- 进度不自己走:没有内置定时器或假进度,百分比全部由宿主回传,避免出现「看起来在上传其实没动」
- 列表为空时显示空态文案;到
maxCount后触发器改成不可点的提示行
46.6 无障碍
- 触发器是原生
button,带aria-disabled;到上限时用disabled让键盘跳过 - 进度条写
role="progressbar"+aria-valuemin/aria-valuemax/aria-valuenow,aria-label说明是哪个文件的进度 - 删除 / 重试按钮各自带
aria-label(含文件名),读屏播报「删除 合同扫描件.pdf」而不是孤零零的「删除」 - 状态文案是文字而不只是颜色(「已上传」「上传失败」),颜色不作为唯一信息通道
46.7 doNotInvent
- 真实的传输:请求、分片、断点续传、并发数(本组件只回传事件,不发任何请求)
- 图片压缩、裁剪、水印与方向纠正
- 服务端的校验规则(大小上限、类型白名单以文案与
accept表达,不代为判断) - 拍照与相册的原生调起(由宿主在
select事件里自行调用平台 API)
46.8 unknowns
- 上传中能否同时继续添加文件(当前允许,列表各自独立)
- 失败自动重试的次数与退避策略
- 是否需要在成功行上展示服务端返回的文件 id / URL
47 · 表格 Table
47.1 用途
把多条同构记录按列并排,用于「订单列表 / 库存明细 / 对账单」这类需要逐行横向对比的场合;移动端与桌面端的关键差别是宽度根本不够:桌面上 8 列一眼看完,375px 的手机上同样 8 列每列只剩 47px,文字全部折断。因此移动端表格必须做「在组件自己的容器里横向滚动」——绝不能让页面整体横向溢出,否则整页会跟着左右晃、顶栏与底部栏一起位移;列更多或更该读字段名时改用 mode=card,把每行摊成键值对,彻底不需要横滚。
47.2 结构(anatomy)
table:根元素,纵向排列「视口 + 空态」table__viewport:横向滚动的唯一发生点(overflow-x: auto),页面整体不横向溢出table__inner:原生table,min-width由--kole-m-table-min-width给出;超过容器即滚table__caption:可选表格标题(说明这张表在讲什么)table__head/table__head-cell:表头与表头单元格,position: sticky固定在滚动视口顶部table__row/table__cell:数据行与单元格,行高不小于 44px;单元格可带data-label(卡片模式的字段名来源)table__empty:空态占位,role="status"+aria-live="polite"
47.3 变体维度
mode:scroll(横向滚动,列多时用)/card(每行摊成键值对,小屏彻底不横滚)size:default(常规行高)/compact(收紧内边距与字号;行高仍不小于 44px)stripe:false(纯白底)/true(偶数行浅底,便于横向扫读时对齐行)bordered:false(只有横线)/true(单元格之间也有竖线)
47.4 状态
- default:常态
- selected:当前选中行,
is-selected与aria-selected同步(行选中不是复选框,单行高亮即可) - disabled:整行置灰且不响应,写
aria-disabled="true" - empty:没有数据时的占位文案(列宽语义不变,不会把容器撑宽)
- clickable:行可点(
clickable=true时整行都是热区,按下反馈是整行背景变化)
47.5 交互与触控
- 横向滚动发生在
table__viewport上:根与视口都写min-width: 0,否则内部min-width会把父级顶宽、页面整体横向溢出 - 选项行高不小于 44px;触屏上横向滑动不被父级纵向滚动吞掉(
touch-action: pan-x pan-y) - 表头用
position: sticky而不是fixed——fixed会脱出滚动容器,横向滚动时表头不跟随 - 长文本列默认单行省略,完整值放
title;数字列用等宽字右对齐,便于按位对比 - 点整行即选中(同表互斥);行内的独立控件(按钮 / 链接)要阻止冒泡,避免一次点击触发两个动作
- 行可点时同时提供键盘路径(聚焦后回车 / 空格),不把鼠标点击当成唯一入口
47.6 无障碍
- 用原生
table/thead/tbody/tr/th/td,表头写scope="col"(读屏能报出列名) - 空态用
role="status"+aria-live="polite"播报占位文案 - 禁用行写
aria-disabled="true",读屏播报不可用 - 可点行必须同时可聚焦:
clickable=true时行写tabindex="0"+role="row"+aria-selected,聚焦后回车 / 空格即选中 —— 只有鼠标点击而没有键盘路径是缺陷,不是简化 - 焦点环画在行内第一个单元格上(
tr上的outline在部分浏览器不渲染) - 行内的独立控件(链接 / 按钮)有可见焦点环,并阻止冒泡
mode=card时字段名由data-label生成并参与读屏(键值对在视觉与语义上都成立)- 可点行的
cursor: pointer只给表体行:表头行不承载点击,跟着变手型会把「这里能点」这个信号弄错
47.7 doNotInvent
- 虚拟滚动与无限加载的触发规则(由宿主容器负责;本组件不接管纵向滚动)
- 服务端排序 / 筛选 / 分页(排序图标与请求都由宿主发起)
- 列宽拖拽调整与列显隐设置(移动端没有这个操作面)
- 冻结列(
position: sticky的横向版本;当前只固定表头的纵向位置)
47.8 unknowns
- 表头是否需要在纵向滚动时也吸顶(当前只在宿主给固定高度时生效)
- 行选中是单选还是多选(当前单选;多选应交给 Checkbox 列)
- 卡片模式下字段名的排列是「左标签右值」还是「上标签下值」