关于 HTML 列表的一些新知识

Avatar of Daniel Schwarz
Daniel Schwarz

DigitalOcean 为您旅程的各个阶段提供云产品。立即开始使用 $200 免费积分!

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 的说法,它们应该用于元数据、词汇表和其他类型的键值对。我建议避免使用它们,因为所有屏幕阅读器都会以不同的方式宣布它们。

但让我们不要以负面情绪结束。这里列出了一些你可以用列表做的超级酷的事情: