<ul> 不只是换行工具,其核心价值在于为屏幕阅读器提供“逻辑相关项目组”的语义,需正确嵌套 <li> 包裹完整表单项、避免语义打断,并配合 <fieldset>/<legend> 分组及 aria-labelledby 建立层级关系。
很多人用 <ul> 包裹表单项,只图它默认换行、结构清晰。但真正价值在于:屏幕阅读器会把 <ul> 识别为“一组逻辑相关的项目”,并播报“列表,共3项”,再逐项读出内容。这比一串孤立的 <div> 或语义错乱的 <p> 强得多。
关键点是:<ul> 本身不等于可访问——它需要配合正确的嵌套和语义元素才能生效。
<li> 必须直接包裹完整表单项(<label> + <input> 等),不能只包 <label> 或只包 <input>
<li> 内再嵌套 <div> 或 <p> 来“控制样式”,否则可能打断列表语义流<fieldset> + <legend>,再在其内部用 <ul>,而不是靠 CSS 隐藏 <legend>
当一个 <ul> 是子级(比如“支付方式”下的多个信用卡选项),单纯靠缩进或视觉样式无法让屏幕阅读器感知层级。此时不能只依赖 CSS,而要显式建立引用关系。
常见错误是给 <ul> 加 aria-label,但这会覆盖掉原本的列表项播报;更稳妥的做法是用 aria-labelledby 指向一个已有标题元素:
立即学习“前端免费学习笔记(深入)”;
<h3 id="payment-methods">)真实存在且可见<ul aria-labelledby="payment-methods"> 会让屏幕阅读器先读“支付方式”,再读“列表,共2项”,再读每项内容aria-label 替代 <legend> 或 <h3>,因为前者不可被用户搜索、跳转或翻译<ol>
<ol> 的语义不是“带数字”,而是“顺序重要”。例如步骤引导、评分标准、法律条款条目——这些场景下,改变项序会改变含义。
但注意:<ol> 的默认数字样式(1. 2. 3.)对屏幕阅读器影响极小,真正起作用的是 DOM 顺序和 aria-posinset/aria-setsize(极少需手动加)。所以:
<ul>,别为了数字强行用 <ol>
<ul>
<ol>
如果 <li> 里放了开关、复选框或自定义按钮,键盘用户 tab 导航时容易卡住——因为 <li> 默认不可聚焦,而内部控件又没处理好 tabindex 和角色。
最简方案是保持原生语义:
<li> 里,不用额外 wrapper,它们自带可聚焦性<li> 加 tabindex="0",否则会多出一个无效焦点停靠点role="checkbox" 或 role="switch",并同步管理 aria-checked 和键盘事件(Space/Enter)复杂交互列表最容易被忽略的,是当某项被禁用(disabled)时,整个 <li> 应该视觉灰化且跳过焦点,而不是仅禁用内部控件却留空可聚焦的外壳。