如何处理复选框列表的过滤器和结果按钮的可访问性?

问题描述 投票:0回答:1

假设我正在尝试使用复选框列表构建多选输入:

  • 我已将复选框放在带有
    <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>
标签来执行我想要执行的操作吗?

javascript html css accessibility wai-aria
1个回答
0
投票

我想说这已经是一个非常复杂的实现了。

但是谁能说是否可以使用

<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>

 元素 [...]

© www.soinside.com 2019 - 2024. All rights reserved.