如何正确构建HTML表单。可访问性

139 阅读16分钟

DZone>Web Dev Zone>如何正确构建HTML表单。可访问性

如何正确地构建HTML表单。可访问性

表格可以说是任何网络应用中最重要的部分。这篇文章涵盖了创建HTML表单的关键概念,以实现无障碍性。

Austin Gil user avatar通过

奥斯汀-吉尔

-

Aug. 19, 21 - Web Dev Zone -教程

喜欢 (1)

评论

保存

鸣叫

11次浏览

加入DZone社区,获得完整的会员体验。

免费加入

表格可以说是任何网络应用的最重要部分。没有表单,我们就不会有像Google、Facebook、Amazon、Reddit等网站。然而,我越是浏览网络,就越是看到表单的糟糕实现。

在这个系列中,我们将研究为网络创建表单的正确步骤,如何思考我们所写的代码,以及在这个过程中需要考虑的问题。这个系列分为以下几个部分。

目录

  • 语义学
  • 标签
  • 视觉上隐藏的 "实用类
  • 名称 "属性很重要
  • 保持你的状态不变
  • A11y工具
  • 其他资源

语义学

上一篇文章我们探讨了语义以及我们通过编写语义HTML所获得的所有好处。我们简单地谈到了可访问性,但还有很多可访问性方面的好处,我们将在这里更详细地介绍。首先考虑这些是有意义的。

语义学如何对可及性产生积极影响的一个很好的例子是在无序列表中发现的。你可能在某个地方看到过创建菜单的语义方式,也可能没有。

<nav>
  <ul>
    <li>
      <a href="...">Menu item</a>
    </li>
    <!-- other items -->
  <ul>
<nav>

考虑到<nav> 已经让使用辅助技术的用户知道他们是在一个导航元素里面,而且这个链接是,嗯,是一个链接,这可能看起来相当冗长和不必要。

然而,我们从使用<ul><li> 中得到的好处是,依赖辅助技术的用户可以得到他们在一个列表中的细节,列表中有多少个项目,以及他们目前在哪个列表项目上。你可以想象,如果你不能看到列表,知道它是一个简短的列表还是一个非常长的列表可能会很好。

正如我之前提到的,前面的文章更详细地介绍了语义学。我只是想在这里提到语义学在无障碍性方面所起的作用(双关语)是多么重要。语义学的关键启示是:总是使用<form> 标签,总是包括<label> (或aria-label ),使用适当的输入type 属性,并且总是包括某种提交按钮(即使它不可见)。

标签

网络上有很多关于无障碍性的文章。他们中的大多数人都把标签作为第一要务,这也是正确的。任何没有标签的表单输入都是一个主要的可访问性问题,但不是所有的标签都是一样的。

正如我们在上一篇文章中所看到的,有4个选项可以为一个输入添加标签。

  • 显式标签for 属性引用输入的ID)。
    <label for="name">Name:</label><input name="name">
  • 隐式标签(<label> 元素包裹着输入)。
    <label>Name: <input name="name"></label>
  • **title**:
    Name: <input name="name" title="Name">
  • **aria-label**:
    Name: <input name="name" aria-label="Name">

使用显式标签

很长时间以来,我认为显性标签和隐性标签之间没有什么区别,但事实证明有区别。许多辅助设备,如屏幕阅读器,对这些都是一样的。语音控制就不一样了,这也是一个重要的可及性考虑。

事实证明,Dragon Naturallyspeaking是一个非常常见的语音控制软件,但它没有通过隐性标签的无障碍测试

标题可以被辅助技术拾取,也可以作为表单控制的标签的替代。据我所知,它们工作得很好,但标题的问题是它们经常被错误地使用。将标签和标题结合起来可能会导致不必要的或多余的信息。在可能的情况下,包括视觉标签也被认为是一个好的做法。

但是,关于 [aria-label](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-label_attribute)s?

aria-label视觉标签是我们可及性工具带的一个重要补充。它们给我们提供了为非文本元素提供背景的能力。这对辅助技术的使用者来说是至关重要的。

然而,对于依赖客户端翻译工具的用户来说,有一些潜在的问题。例如,一些用户可能以你的语言登陆你的网站,而他们可能讲另一种语言。假设他们有一个浏览器扩展,可以将页面内容翻译成他们自己的语言。如果他们这样做,你的aria-label,可能会被遗漏。

谷歌的Chrome浏览器有内置的翻译支持,他们解决了这个问题。它现在支持翻译aria-labels,而且我可以确认我已经测试过了。这很好,但它并不适用于所有客户端的翻译器。

目前,我将继续尽可能地使用明确的标签,以备我需要支持多语言的观众。但我可能会在这里和那里扔一个aria-label ,以保持事情的简单。

让事情变得简单

所以到现在为止,我们已经决定对我们所有的输入使用明确的标签。这很好,但有一件令人讨厌的事情。它们要求我们在<label> 上包含一个for 属性,在<input> 上包含一个id 属性。这可能很繁琐,也比较啰嗦,而且如果我们不知道页面上所有其他ID的列表,就很难想出真正独特的ID。如果我们创建的组件可以在整个网站的任何地方使用,那就更麻烦了。

幸运的是,这也可以很容易。只要让机器人做机器人最擅长的事情。ID不需要有任何意义,如果我们只是用它们来连接带有标签的表单,我们也不需要知道它们的价值。因此,创建一个随机字符串生成器是一个很好的工具。

如果你使用的是JavaScript,它可能看起来像这样。

/**
 * @param {number} [length=10]
 * @pram {string} [allowed='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'] 
 * @return {string}
 */
function randomString(length = 10, allowed = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789") {
  let result = ""
  for (let i = 0; i < length; i++) {
    result += allowed.charAt(Math.floor(Math.random() * allowed.length))
  }
  return result
}

你可以在任何时候使用这个函数来生成你的HTML,将一个随机字符串分配给一个变量,并在你的模板中使用for 属性和id 。这在组件驱动的架构中效果特别好,但你要确保考虑到任何明确定义了ID的情况,如果没有提供ID,则退回到一个随机生成的。

视觉上隐藏的 "实用类

前面的观点很好。我们都想创建可访问的输入,所以我们决心在所有的输入上贴上标签,为了我们的国际读者,我们避免使用aria-label 属性。生活是美好的。

但是,如果我们的设计师交给我们一个搜索功能的原型,它只是一个带有一些占位符的输入,上面写着 "输入你的搜索词并按下'Enter'",那该怎么办?现在我们有一些问题。

  • 没有明显的标签(占位符不能代替)。
  • 没有提交按钮

为了解决想写好的、有语义的、可访问的标记和坚持不包括必要元素的设计这一难题,我们可以像这样在我们的样式表中添加一个类。

.visually-hidden {
  position: absolute;
  overflow: hidden;
  clip: rect(0 0 0 0);
  width: 1px;
  height: 1px;
  margin: -1px;
  border: 0;
  padding: 0;
}

我把这个实用类添加到我创建的几乎每个项目中。它对那些有视觉上隐藏的标签和/或提交按钮的表单非常有效,比如上面的搜索框例子。

<form>
  <label for="search">
    <span class="visually-hidden">Search</span>
    <input id="search" name="search" />
    <button class="visually-hidden">Submit</button>
  </label>
</form>

现在我的表单有一个无障碍标签,它对翻译很友好,而且我们为屏幕阅读器用户提供了良好的用户体验。

但这并不完美。像我这样喜欢在表单中使用键盘导航,但也依赖视觉提示的用户,可以在表单中使用标签,达到输入,然后到达提交按钮,我们的焦点状态就会丢失,因为提交按钮在视觉上是隐藏的。

一个简单的解决方案是在表单提交按钮获得焦点后,为其添加样式,以显示一个绝对或固定位置的高对比度按钮。

button.visually-hidden:focus,
input[type="submit"].visually-hidden:focus {
  position: fixed;
  top: 10px;
  left: 10px;
  clip: unset;
  width: unset;
  height: unset;
  border: 3px solid;
  padding: 5px;
  background: #fff;
}

请注意,这更像是一个主观的决定。有很多方法可以将按钮添加到页面上,但重点是你应该添加它。如果你不喜欢这种方法,另一种好的方法是在作为提交按钮的表单中添加一个搜索图标。

在这个例子中,我使用了一个SVG的roleimg ,结合了一个视觉上隐藏的span 。你也可以使用一个带有适当的alt 属性的图片。

<form>
  <label>
    <span class="visually-hidden">Search</span>
    <input type="search" >
    <button>
      <svg role="img" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none" stroke="currentcolor" stroke-width="2">
        <circle cx="14" cy="14" r="12" />
        <path d="M23 23 L30 30"  />
      </svg>
      <span class="visually-hidden">Submit</span>
    </button>
  </label>
</form>

你需要在这里多加一些样式来使按钮看起来正确。

名称 "属性很重要

HTML的 [name](https://www.w3schools.com/TAGS/att_input_name.asp)属性可用于多个元素,但在我们的案例中,我们要关注的是它与<input> 元素的关系。默认情况下,当一个表单被提交时,表单数据被传递到请求中(无论是在正文中还是在查询字符串中),这样每个输入的name 对应于它的value

随着JavaScript的大规模应用,我们很容易省略name 属性,因为FormDataURLSearchParams使得提交一个带有你想要的任何数据的表单变得非常简单。

VueReact等框架和库也使得输入值与组件状态中的数据绑定变得容易。当你选择一个单选输入或勾选一个复选框时,state 对象中的相应数据会更新,反之亦然。

问题是,如果没有name 属性,一些输入(特别是radio 类型)的行为就不符合预期。如果你省略了name 属性,每个单选输入都会被当作一个单独的输入,而不是作为一个组中的不同选项。你可以使用tab 键单独访问每个输入并启用/禁用它,但预期行为如下。

  • tab 键应该集中在所选的输入上。如果没有选择,它应该依次循环访问每个输入。
  • shift + 键应该集中在选定的输入上。如果没有被选中,它应该以相反的顺序循环处理每个输入。tab
  • spacebar 如果没有选择,应该选择当前的焦点输入。
  • up / 方向键应选择前一个单选输入。left
  • down / 方向键应该选择下一个单选输入。right

这一切都很好,但一个例子确实能起到更好的作用。试着只用你的键盘来浏览这组输入。

将其与没有name 属性的一组收音机进行比较(并记住上面的预期按键)。

这都是为了说明,当键盘导航的用户(像我一样)在键盘工作时,会感谢你的。

名称对开发者也有好处!

在你的表单中为每一个输入添加name 属性,实际上也使你的开发者的生活更轻松。它通过结合 FormData 和 URLSearchParams 使得提交表单变得简单。

const form = document.querySelector("#your-form")
const url = "https://jsonplaceholder.typicode.com/posts"

form.addEventListener('submit', e => {
  e.preventDefault()

  const data = new FormData(e.target)
 
  fetch(url, {
    method: 'POST',
    body: new URLSearchParams(data)
  })
})

这一点非常好,我已经很少在我的Vue或React表单中使用输入绑定了。还要注意的是,如果你可以直接将FormData作为请求主体发送,但它会将HTTP头信息设置为multipart/form-data (通常不是我想要的)。

保持你的状态

到目前为止,在帮助使用辅助技术的用户理解表单输入,以及帮助键盘用户能够浏览他们的表单方面,我们已经覆盖了很多地方。接下来,我们应该把重点放在提供有关表单输入状态的正确信息上。

必需的

HTML5输入验证器让我们可以很容易地将一个输入字段标记为必填,只需添加 [required](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/required)属性。

<input name="example" required>

通过简单地将这个属性添加到所需的输入中,你可以为一些辅助技术提供更多的背景。

有一点需要注意的是,如果你在标签文本旁边使用星号(*)来表示必填的输入,你就会帮屏幕阅读器的忙,用一个 [aria-hidden](https://developer.mozilla.org/en-US/docs/Web/Accessibility/ARIA/ARIA_Techniques/Using_the_aria-hidden_attribute)属性包裹它,这样它就不会被大声读出来。

<label for="example">
  Label text <span aria-hidden="true">*</span>
  <input id="example" name="example" required>
</label>

或者,如果你选择用CSS [content](https://www.w3schools.com/CSSref/pr_gen_content.asp)属性来添加星号,一定要记得包含CSS [speak: never;](https://css-tricks.com/almanac/properties/s/speak/)规则,这样它就不会意外地被屏幕阅读器读取。

失能

在某些情况下,你可能需要禁用一个输入。再一次,用HTML [disabled](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/disabled)属性来实现。

<input name="example" disabled>

从可访问性的角度来看,这将影响键盘导航,使输入被跳过。我想这取决于你的应用程序的需要,以知道这是否是正确的行为,但知道它是如何工作的很重要。

焦点

正确地管理焦点状态是可访问性中最具挑战性的部分之一。这对那些视觉正常,使用键盘导航的用户(像我一样)很重要。这类用户依赖浏览器的焦点状态来浏览网站,所以你绝对不应该完全删除焦点环。不过,你可以改变它。

一个很容易弄糟的实际例子是自定义复选框或单选输入的常见模式。不幸的是,没有简单的方法来定制原生的用户界面,所以人们想出了一个方法来隐藏原生的,并创建他们自己的自定义的。

我们将在以后的文章中更详细地讨论实现自定义输入设计的问题。今天你只需要知道,我们感兴趣的标记看起来是这样的。

<input name="example" type="checkbox" class="visually-hidden" />
<span class="custom-checkbox">Input label text</span>

这个输入在视觉上是隐藏的,这意味着它将从键盘导航中获得焦点,但没有人能够看到它被关注。我们可以用一个方便的CSS片段来解决这个问题,该片段将一个自定义的焦点环应用于<span> ,每当输入被聚焦时,看起来就像本地浏览器的样式。

input:focus + .custom-checkbox {
  outline-width: 1px;
  outline-style: auto;
  outline-color: Highlight;
  outline-color: -webkit-focus-ring-color;
}

无效

在表单中,我们经常有一些标准,在接受表单之前,输入必须满足这些标准。例如,用户名不能有某些字符,电子邮件必须是电子邮件,密码必须长于某个最小值。很多时候,当用户输入无效的数据时,我们要通过向他们显示一些反馈来让他们知道。这是一件好事,为辅助技术提供这种反馈也同样重要。

首先,我们可以再一次依靠HTML的内置验证属性。这些属性可以告诉一些辅助技术在填写输入时应该期待什么。

我们还可以靠另外两个ARIA属性来改善我们的体验。 [aria-invalid](https://www.w3.org/TR/WCAG20-TECHS/ARIA21.html)[aria-describedby](https://www.w3.org/TR/WCAG20-TECHS/ARIA1.html).就像我们可以用颜色来表示一个输入是无效的,用错误信息来说明错误是什么一样,我们可以用这些工具来为非视觉用户提供同样的反馈。事实上,一旦我们有了其他的设置,这样做是非常简单的。

<label for="my-input">
  Example:
  <input id="my-input" name="example" minlength="3" aria-invalid="true" aria-describedby="description" oninput="this.setAttribute('aria-invalid', !this.validity.valid)" />
<label>
<p id="description">Must be at least 3 characters.</p>

这个例子可以说明问题,但在你的应用程序中,你可能希望事件监听器在一个单独的文件中,也许它可以更新描述中的错误信息。

A11y工具

好了,现在我们已经快要到达这个漫长旅程的终点了,也许你已经意识到你还有一些工作要做。我知道,一开始它可能看起来很艰巨。幸运的是,有一些工具可以帮助我们。

了解你所处的位置

如果不对项目进行测试,就很难知道我们做得多好或多差。为了很好地了解我所处的位置(假设我不是从头开始),我喜欢让机器人做机器人擅长的事情,比如审计我的网站。

当你编码时

一旦我们运行了审计并修复了所有的代码,我们要确保在未来避免任何可访问性问题。再一次,我们可以在这里向机器人寻求帮助。

迫使你的团队承诺

自己测试东西和建立自己的无障碍代码环境是一回事,但如果我们团队的其他成员不加入这个努力,可能就不会成功了。如果你能说服你的团队这是值得的,那么你可以通过添加工具到你的开发管道来强制执行良好的可访问性实践。

  • pa11y-lint-config是一个ESLint插件,它将通知你代码中的错误。你甚至可以配置它来拒绝,或者(如果可能)自动修复提交钩上的任何错误。
  • Pa11y.org还有一个CI/CD工具,你可以把它引入到你的构建管道中,它可以对网站进行审计,如果审计不通过,就阻止任何构建被部署到生产中。

让别人来做这些工作

最后,确保你正在构建的东西是可访问的,最好的方法之一是采用一个已经做好可访问性的框架。有些人做得很好,有些人则不那么好。所以要确保如果你要抓住一个框架,它是一个能提高你的生产力和可及性的框架。

  • 如果你正在使用React,Reakit看起来不错。我没有用过它,但我看了看输出的HTML。
  • 对于Vue.js的开发者,VuetifyBootstrap-vue做得很好,我自己的Vuetensils特别关注可及性。
  • 我没有太多的其他经验,但我认为基本的Bootstrap框架也注重于无障碍。

这些工具中有许多是通用的,而不仅仅是用于表单。有些工作特别好,但在许多情况下,它们不能取代手动检查你的代码或让用户实际测试你的应用程序。

当涉及到无障碍性时,通过测试并不总是一个合格的经验。

其他资源

多年来,我阅读了许多优秀的文章。不幸的是,我没有太多的书签,但当我在写这篇文章时,我确实看到了几篇非常有趣的文章,我想与大家分享。

  • Dave Rupert汇编了几篇文章,指出了需要了解的非常好的可及性问题。
  • 这位作者有一篇关于无障碍组件的伟大文章。
  • Heydon Pickering有一个关于包容性组件的伟大网站。它不是真正关于表单的,但仍然很好。
  • 最后,WAI-ARIA的指南看起来让人不知所措,但实际上充满了伟大的资源和例子。

非常感谢您的阅读。如果你喜欢这篇文章,请分享它,如果你想知道我何时发表更多的文章,请注册我的新闻通讯在Twitter上关注我。干杯!

主题。

html, css, web开发, 表单, 前端, 可访问性, 编码, 开发, 编程, 软件

由Austin Gil授权发表于DZone。点击这里查看原文。

DZone贡献者所表达的观点属于他们自己。

DZone上的热门文章


评论

网络开发 合作伙伴资源