很多代码评审里,“列表”常被简化成两个答案:有顺序用 ol,没顺序用 ul。

Frank M. Taylor 在 5 月 13 日发布的《You don’t know HTML Lists》有意思的地方,正是把这个习惯掰开了看。HTML 里的列表不只是 ul 和 ol。select、datalist、dl、menu,也都在处理某种“成组内容”。

真正要问的不是它长什么样。

而是:这组内容在语义上是什么关系,用户能不能操作,输入是否被固定选项约束。

判断列表,先判断关系

原文给出的主线很直接:不要从视觉出发,要从内容关系出发。

如果顺序一变,意思就变,用 ol。比如步骤、排名、时间线。这里的标准不是“页面上需不需要数字”,而是“顺序是否参与表达”。数字样式可以交给 CSS,语义不能靠 CSS 补回来。

如果只是一组普通项目,才默认用 ul。很多导航、标签集合、普通条目列表都属于这一类。

如果是术语解释、键值关系,或一个键对应多个值,用 dl。比如参数名和参数说明、术语和解释、作者和多篇作品。它表达的是“描述关系”,不是普通项目堆叠。

如果是一组界面动作,可以考虑 menu。但这里要加一句限制:menu 不会自动给你一个完整的应用菜单交互。浏览器呈现和可访问性支持也不能按理想状态想象。它更适合表达“这里是一组命令”,具体按钮、键盘行为和可访问性测试仍然要做。

场景更合适的结构判断依据
固定选项,只能从列表中选select + option用户不能输入列表外的值
固定选项,允许多选select multiple原生支持多选语义和交互
相关选项分组optgroup选项之间有分组关系,可按需 disabled
建议型输入input + list 绑定 datalist用户仍可输入非建议值
顺序改变会改变含义ol判断标准是意义,不是数字样式
键值、术语解释、一键多值dl内容是描述关系
一组界面动作menu项目本身是命令或操作
普通集合ul没有顺序、描述或动作语义

这张表背后的判断很简单:标签不是装饰容器。它是在告诉浏览器、辅助技术和维护者,这组内容到底是什么。

select 和 datalist 最容易被混用

表单控件列表,是这篇文章最该被前端团队记住的部分。

固定选项用 select + option。用户只能从列表中选,不能随便输入列表外的值。如果允许多选,加 multiple。如果选项有相关分组,用 optgroup;某组暂不可选时,可以 disabled。

这套东西看起来老,但它有浏览器内建能力。键盘操作、表单提交、可访问性语义,很多都已经存在。用 div、ARIA role 和 JavaScript 重造一个“长得像 select 的控件”,往往不是升级,而是接手一堆边角债。

datalist 的边界不同。它不是 select 的替代品,更像输入建议。

开发者用 input 的 list 属性绑定 datalist。用户可以点建议,也可以输入列表之外的内容。这会影响后端校验、数据清洗和产品预期。产品如果要的是“只能选省份”,datalist 就不合适;如果要的是“给用户几个常见输入建议”,它才合适。

还有一个细节容易踩坑:datalist 里的 option 如果设置了 value,进入输入框的是 value,不一定是用户看到的标签。比如显示是“Welsh”,value 是“cy”,用户选中后,输入框可能出现“cy”。

在 select 里,用户通常看到标签,提交时走 value,这很正常。放到 datalist 里,value 会直接进入输入框,体验就变了。

原文还提到 datalist 可以配合 week、range 等输入类型。但展示效果不能假设跨浏览器一致。尤其是 range 的标记和样式控制,不同浏览器表现会有差异。这里适合审慎使用,不适合只看规范就上线。

对前端团队,动作要落到组件规范

这件事最影响两类人:写业务页面的前端,以及维护组件库、设计系统的人。

业务前端可以先改一个习惯:写列表前停一下,问它到底是“展示集合”,还是“表单选项”,还是“动作集合”。这一步能减少很多后续补丁。

维护组件库的人动作更具体:不要把所有东西都做成一个 List 组件,再靠 props 分支撑所有场景。下拉选择、建议输入、菜单命令、描述列表,应该在组件规范里分清楚。

更现实的做法是三条:

  • 固定选项优先封装 select 语义,不要默认重造下拉框。
  • 建议输入单独处理 datalist 或自定义补全,并明确允许非列表值。
  • 菜单、工具栏、操作组要把按钮语义、键盘行为和屏幕阅读器结果一起验收。

我不太买账的是,把“原生控件不好看”当成重造控件的默认理由。样式难调是真的,设计一致性也是真的。但一旦重造,就要自己补键盘导航、焦点管理、读屏提示、禁用态、表单提交和异常输入。

框架也不会替你做这个判断。React、Vue、Svelte 负责状态和渲染,不负责判断一组内容是 ol、dl、menu,还是 select。工程链路越重,越不能把基础语义当小事。

后面真正该看的,不是哪个标签突然流行。而是团队的组件规范里,有没有把“内容关系”和“交互意图”写清楚。写清楚了,很多债不用借;写不清楚,迟早在无障碍、测试和表单数据里还。