假设我正在尝试使用复选框列表构建多选输入:
<fieldset>
标签的 <legend>
内<details>
和 <summary>
内部,使其具有传统的选择框外观。这是工作演示:
("use strict");
// Focus on the filter input when the options are expanded/opened
document.querySelectorAll(".options-details").forEach(function (details) {
details.addEventListener("toggle", function () {
if (details.hasAttribute("open")) {
details.querySelector(".filter-input").focus();
}
});
});
// Filter options
document.querySelectorAll(".filter-input").forEach(function (input) {
input.addEventListener("input", function () {
const options = input.closest("fieldset").querySelectorAll(".btn-check");
options.forEach(function (option) {
option.classList.remove("d-none");
const value = option.value.toLowerCase();
if (value !== "" && !value.includes(input.value.toLowerCase().trim())) {
option.classList.add("d-none");
}
});
});
});
// Add option button when an option is checked
document.querySelectorAll(".btn-check").forEach(function (input) {
input.addEventListener("input", function () {
if (input.checked === true) {
const optionButtonItem = document.createElement("li");
optionButtonItem.innerHTML = `<button class="option-button btn btn-primary" type="button" id="${input.getAttribute("id")}-remove" aria-describedby="option-remove">${input.getAttribute("value")}</button>`;
input.closest("fieldset").querySelector(".option-buttons-list").appendChild(optionButtonItem);
// Add event listener to remove option and button and uncheck option
const optionButton = document.getElementById(`${input.getAttribute("id")}-remove`);
optionButton.addEventListener("click", function () {
document.getElementById(optionButton.getAttribute("id").slice(0, -7)).checked = false;
optionButton.closest("li").remove();
});
} else {
document
.getElementById(`${input.getAttribute("id")}-remove`)
.closest("li")
.remove();
}
});
});
:root {
--bs-font-size-base: 1rem;
--bs-font-size-sm: 0.875rem;
--bs-font-size-lg: 1.125rem;
}
.option-button::after {
content: "\00D7";
margin-left: 12px;
}
.options-container {
min-height: 36px;
max-height: 300px;
}
.btn-check.d-none + .btn-secondary {
display: none;
}
.btn-check:checked + .btn-secondary {
background-color: var(--bs-primary) !important;
color: var(--bs-primary-foreground) !important;
}
.btn-check:focus-visible + .btn-secondary {
background-color: var(--bs-primary-100) !important;
box-shadow: none !important;
}
.btn-check:checked:focus-visible + .btn-secondary {
background-color: var(--bs-primary-active-bg) !important;
}
<link href="https://cdnjs.cloudflare.com/ajax/libs/Halfmoon/2.0.1/css/halfmoon.min.css" rel="stylesheet"/>
<body class="min-vh-100 d-flex align-items-center justify-content-center">
<div class="specific-w-600 mw-100 p-5">
<fieldset>
<legend class="form-label">Select the languages you want*</legend>
<p class="text-body-secondary mb-4">
Let's get started with the survey. First, please choose all
of the languages that you know.
</p>
<span id="option-remove" class="visually-hidden">remove</span>
<ul
id="options-selected"
class="option-buttons-list list-unstyled d-flex flex-wrap gap-1 m-0"
></ul>
<details class="options-details border rounded mt-2 overflow-hidden">
<summary class="px-3 py-2">
Choose one or more options
</summary>
<div class="border-top">
<div class="p-3 border-bottom">
<span id="filter-label" class="visually-hidden">Filter languages</span>
<input
type="text"
class="filter-input form-control"
placeholder="Filter languages"
aria-autocomplete="list"
aria-labelledby="filter-label options-selected"
/>
</div>
<div
class="options-container d-flex flex-column overflow-y-auto"
>
<input
type="checkbox"
class="btn-check"
id="checkbox-1"
value="Python"
autocomplete="off"
/>
<label
class="btn btn-secondary text-start px-3 py-2 lh-sm border-0 rounded-0"
for="checkbox-1"
>
Python
</label>
<input
type="checkbox"
class="btn-check"
id="checkbox-2"
value="JavaScript"
autocomplete="off"
/>
<label
class="btn btn-secondary text-start px-3 py-2 lh-sm border-0 rounded-0"
for="checkbox-2"
>
JavaScript
</label>
<input
type="checkbox"
class="btn-check"
id="checkbox-3"
value="C++"
autocomplete="off"
/>
<label
class="btn btn-secondary text-start px-3 py-2 lh-sm border-0 rounded-0"
for="checkbox-3"
>
C++
</label>
</div>
</div>
</details>
</fieldset>
</div>
</body>
JavaScript 执行以下操作:
我的问题是:我当前的设置有多方便?在过滤器输入、结果按钮等上使用正确的 WAI-ARIA 标签方面。此外,可以在此处使用
<details>
和 <summary>
标签来执行我想要执行的操作吗?
我想说这已经是一个非常复杂的实现了。
但是谁能说是否可以使用
<details>
元素呢?就标准而言,没问题。最好现在就与辅助技术的用户一起测试它。
aria-autocomplete
注意到这一段:
如果某个元素的
设置为aria-autocomplete
[…],作者 必须 确保满足以下两个条件:list
- 该元素具有为
指定的值,该值引用包含建议值集合的元素。aria-controls
- 该元素具有
值,该值与包含建议值集合的元素的角色相匹配。aria-haspopup
aria-haspopup
:
[…] 作者必须确保充当弹出内容容器的元素的角色是
、menu
、listbox
网格或tree,
[…]dialog
反过来,
listbox
只能将option
作为可访问子项,而不是checkbox
。
所以你需要重新制定清单。请参阅我对第二个问题的回答,了解有关可访问选项列表(键盘可访问性、跳过链接)的更多详细信息。
<input
aria-controls="language-list"
aria-haspopup="list"
/>
</div>
<ul id="language-list" role="listbox" aria-multiselectable="true">
<li role="option" aria-selected="false" tabindex="0">Python</li>
…
这样做的好处是,屏幕阅读器可以宣布项目的数量,帮助用户决定是否过滤。
我刚刚使用 NVDA 测试了问题中的示例,我注意到这些细节:
带有删除按钮的列表缺少标签,以明确列出的语言是什么:
<ul … aria-label="Selected languages"></ul>
× 是一个视觉指示器,最好对辅助技术隐藏起来。通过CSS添加的
content
仍然是内容。该按钮的含义如下:
Python 时间按钮删除
<button aria-label="Python. Remove">Python</button>
添加到搜索输入名称中的选定选项也包括“时间”,但这可以通过上述建议解决。
将选定的选项添加到输入的名称是多余的,因为选择列表在之前和之后都可用。完善的 combobox 模式也无法做到这一点。
焦点在折叠的详细信息摘要元素上不可见。
代码中的小细节:
您可以考虑使用
document.createElement()
元素,而不是使用
<template>
,这会使生成的 DOM 结构难以阅读
autocomplete
在
<input type="checkbox">
上没有用:
它可用于以文本或数值作为输入的
<input>
元素 [...]