HTML 列表很无聊。它们的功能并不多,所以尽管它们被广泛使用,我们并没有真正去思考它们。而且我们仍然能够像以前一样对它们进行自定义,比如移除标记、反转顺序以及创建自定义计数器。
但是,在使用列表时,有一些“新”的东西需要了解,其中也包括一些风险。这些风险大多是次要的,但比你想象的要普遍得多。我们将探讨这些风险,以及我们可以用列表做的一些新东西,甚至包括一些解决旧问题的新方法。
澄清一下,我们指的是以下 HTML 元素:
- 有序列表
<ol>
- 无序列表
<ul>
- 描述列表
<dl>
- 交互式列表
<menu>
有序列表、无序列表和交互式列表包含列表项 (<li>
),它们会根据列表的类型以不同的方式显示。有序列表 (<ol>
) 会在列表项旁边显示数字。无序列表 (<ul>
) 和菜单元素 (<menu>
) 会在列表项旁边显示项目符号。我们称之为“列表标记”,并且可以使用 ::marker 伪元素对它们进行样式设置。描述列表使用描述术语 (<dt>
) 和描述细节 (<dd>
),而不是 <li>
,并且没有列表标记。它们应该用于显示元数据和词汇表,但我从未在实际应用中见过它们。
让我们从简单的事情开始 - 如何正确地(至少在我看来)重置列表样式。之后,我们将看看几个可访问性问题,然后重点介绍难以捉摸的 <menu>
元素,你可能会惊讶地发现它实际上也是一种列表!
重置列表样式
浏览器会自动应用它们自己的用户代理样式,以帮助在默认情况下实现列表的视觉结构。这很棒!但如果我们想要从一个没有样式意见的空白画布开始,那么我们必须首先重置这些样式。
例如,我们可以非常轻松地移除列表项旁边的标记。这里没有什么新东西。
/* Zap all list markers! */
ol, ul, menu {
list-style: none;
}
但是现代 CSS 有新的方法可以帮助我们定位特定的列表实例。假设我们想要清除所有列表的标记,除了出现在 长篇内容(例如文章) 中的列表。如果我们结合使用新 CSS 伪类函数 :where()
和 :not()
的强大功能,就可以将这些实例隔离,并在这些情况下保留标记。
/* Where there are lists that are not articles where there are lists... */
:where(ol, ul, menu):not(article :where(ol, ul, menu)) {
list-style: none;
}
为什么使用 :where()
而不是 :is()
?:where()
的特异性始终为零,而 :is()
会采用其选择器列表中最特定元素的特异性。因此,使用 :where()
是一个不太强迫性的方法来覆盖内容,而且它本身很容易被覆盖。
UA 样式还会应用填充,以便将列表项的内容与其标记隔开。同样,这在某些情况下是一个非常不错的默认功能,但如果我们已经像上面那样移除列表标记,那么我们也可能需要清除这些填充。这是另一个使用 :where()
的情况。
:where(ol, ul, menu) {
padding-left: 0; /* or padding-inline-start */
}
好的,这将防止没有标记的列表项看起来悬浮在空中。但我们有点将婴儿和洗澡水一起倒掉了,在所有情况下都移除了填充,包括我们之前在 <article>
中隔离的那些。因此,现在那些有标记的列表会从内容框的边缘伸出来。
请注意,UA 样式会在 <menu>
元素上额外应用 40px
。
因此,我们要做的是防止列表标记“悬挂”在容器外部。我们可以使用 list-style-position 属性来解决这个问题。
或者,也许这取决于风格偏好?
列表的新可访问性问题
不幸的是,在列表方面,即使在现代时代,也存在一些可访问性问题。其中一个问题是由于应用 list-style: none;
造成的,就像我们在重置 UA 样式时那样。
简而言之,Safari 不会 将使用 list-style: none
样式化的有序列表和无序列表识别为实际的列表,例如使用屏幕阅读器浏览内容时。换句话说,移除标记也会移除列表的语义含义。解决这个问题的方法是 在列表上应用 ARIA list
角色,在列表项上应用 listitem
角色,以便屏幕阅读器能够识别它们。
<ol style="list-style: none;" role="list">
<li role="listItem">...</li>
<li role="listItem">...</li>
<li role="listItem">...</li>
</ol>
<ul style="list-style: none;" role="list">
<li role="listItem">...</li>
<li role="listItem">...</li>
<li role="listItem">...</li>
</ul>
奇怪的是,Safari 认为这是一个功能 而不是错误。基本上,用户会报告屏幕阅读器宣布了太多列表(因为开发人员倾向于过度使用它们),因此现在只有那些带有 role="list"
的列表才会被屏幕阅读器宣布,这实际上并不奇怪。 Scott O’Hara 对此进行了 详细说明。
第二个可访问性问题不是我们自己造成的(太好了!)。所以,你知道如何 在没有标题的 <section>
元素中添加 aria-label
吗?嗯,有时对没有帮助描述列表的标题元素的列表执行相同的操作是有意义的。
<!-- This list is somewhat described by the heading -->
<section>
<h2>Grocery list</h2>
<ol role="list">
<!-- ... -->
</ol>
</section>
<!-- This list is described by the aria-label -->
<ol role="list" aria-label="Grocery list">
<!-- ... -->
</ol>
你绝对不需要使用这两种方法。使用标题或 ARIA 标签只是添加了上下文,而不是必需的 - 请确保使用屏幕阅读器测试你的网站,并选择最适合该情况的用户体验。
在某种程度上相关的消息中,Eric Bailey 写了一篇关于 为什么以及如何将 aria-label
视为代码气味 的优秀文章。
<menu>
也是列表?
等等,好的,所以你可能想知道我一直插入代码示例中的所有 <menu>
元素。其实很简单;菜单是无序列表,但它们是为交互式项目设计的。它们甚至在可访问性树中被识别为无序列表。
在语义网络的早期,我错误地认为菜单就像 <nav>
,然后再认为它们是用于上下文菜单(或“工具栏”,如 规范中所述),因为这是 HTML 规范的早期版本中的描述。(MDN 对与 <menu>
相关的所有弃用内容都有一个有趣的说明,如果你对此感兴趣。)
然而,如今,这是使用菜单的语义方法:
<menu aria-label="Share article">
<li><button>Email</button></li>
<li><button>Twitter</button></li>
<li><button>Facebook</button></li>
</menu>
我个人认为,<menu>
有一些很好的用例。最后一个示例显示了一系列社交分享按钮,它们被包装在一个带有标签的 <menu>
元素中,值得注意的是,“分享文章”标签提供了一个重要的上下文,有助于描述这些按钮的作用。
菜单绝对必要吗?不。它们是 HTML 路标 吗?绝对不是。但如果你喜欢使用更少的 <div>
,并且觉得该组件可以使用 aria-label
来提供更多上下文,那么它们就在那里。
还有什么其他内容吗?
是的,还有上面提到的 <dl>
(描述列表)元素,然而,MDN 似乎不认为它们与列表相同 - 它是包含术语的组的列表 - 我不能说我真正见过它们的使用。根据 MDN 的说法,它们应该用于元数据、词汇表和其他类型的键值对。我建议避免使用它们,因为所有屏幕阅读器都会以不同的方式宣布它们。
但让我们不要以负面情绪结束。这里列出了一些你可以用列表做的超级酷的事情:
- 创建目录
- 可视化事件时间线
- 将列表转换为水平的逗号分隔列表
- 设置标记样式(我个人不喜欢,但它仍然很酷)
我已经使用
<dl>
列表 20 年了。对于专业开发者来说,利用完整的 HTML 标签目录是很常见的,而不是局限于 4-5 个已知的标签,并且不关心扩展对各种语义可能性的大而美丽的范围的理解。<dl>
列表对于每个项目都有标题的列表非常方便,例如FAQ
部分。我之前也使用
<dl>
来标记标签。我不确定它在 a11y 方面是否符合标准,但它感觉语义很清晰。对于常见问题解答,我使用了,因为有些问题的答案太长了,如果在页面显示时全部打开,会占用太多浏览器空间。
等等,那么这两个例子在实际意义上有什么区别?我怀疑我和很多人一样,不知道 `<menu>` 的存在,现在还在努力理解它。
感谢您撰写这篇文章!
只是一个想法…
任何东西都可以是列表。
如果我们取消了 `ul`、`ol` 和 `li` 的内置样式,为什么不使用自定义元素呢?
从实用角度来看,哪种方式需要更多样式?哪种方式更适合可重复使用的组件?
感觉就像微服务中的依赖关系论点。当我们使用依赖关系时,我们就限制了自身的可能性,并且最终(如果不是立即)会花费更多时间来处理它。
从实际意义上来说,区别不大。`<menu>` 在使用按钮(而不是链接)时语义性更强,因为它应该指示一个交互式元素列表。在理想情况下,搜索引擎会忽略它们(或者可能已经忽略了它们),屏幕阅读器会以不同的方式宣布它们,并且保持一致性。IMO,使用它们不会有什么坏处。
是的,Daniel,`<menu>` 在某些情况下确实“感觉”语义性更强,但我被文章中的一句话“它们甚至在可访问性树中被暴露为无序列表”给弄糊涂了。我在想“浏览器真的会区分这些东西吗?”
抱歉,Glenn,我应该更清楚地说明。`<menu>` 是无序的,但与 `<ul>` 不同,如果这说得通的话?屏幕阅读器会将它们宣布为菜单,不像 `<dl>`,它们在不同的屏幕阅读器中表现不一致。
我明白了。感谢您的解释,Daniel。
我一直认为 `dl` 对于元数据可能很有意义,例如一对多值的键值对,比如标签:foo bar,类别:Baz,作者:gaz。
不过我不确定。
我觉得 `menu` 可能只对非应用程序网站中的表单有用。
我觉得 90% 的情况下,`aria-labelledby` 比 `aria-label` 更可取。
以下是一两个真实的 `<dl>` 例子:
https://atomicarchive.com/resources/documents/effects/glasstone-dolan/glossary.html
https://meyerweb.com/eric/css/
两个例子都很棒!
非常棒的教程。
谢谢。
我访问的大多数关于 Web 开发的网站,`<dl>` 代表定义列表和定义项。我想知道它们是否等同于描述列表和术语?
官方名称是描述列表。
https://mdn.org.cn/en-US/docs/Web/HTML/Element/dl
我认为定义列表只是一个流行的误解。
`<dl>` 的规范发生了变化。以前它用于词汇表时被称为 `definition list`。在使用案例扩展后,它被更改为 `description list`。
一些注意事项(这是一个包含 4 个项目的无序列表,但 CSS-Tricks 的样式吃了子弹,讽刺的是,这篇文章也使用了这种样式)
除非用户自己表示他们想要 ARIA 列表角色,否则可能不要为 Safari/VO 用户添加 ARIA 列表角色。
可能不要为 `<section>` 元素添加 `aria-label`,除非你希望 `<section>` 成为一个区域,在这种情况下,使用 `aria-labelledby` 来引用标题(你应该已经添加了标题)。
The `<menu>` 元素被暴露为一个无序列表;没有不同的可访问性 API,没有不同的平台 API,在浏览器中也没有什么不同。你可以放心地忽略 `<menu>`,除非作为开发人员你更喜欢它。
描述列表确实以不同的方式宣布在浏览器和屏幕阅读器配对中,但 `<dl>` 的支持总体上很好(Safari 是例外),即使你可能不喜欢它的支持方式。
“我们可以用 `list-style-position` 属性来解决这个问题”…如果使用 `list-style-position: inside;`,列表项的内容会在多行上一直扩展到最左侧。在我看来,在这些项目上应用填充,并将位置保留为默认值会更好。
https://codepen.io/Azragh/pen/abKgNez