ARIA: radio role

radio 角色属于一组可勾选的单选按钮,它们位于 radiogroup 中,在同一时间只能有一个单选按钮被选中。

描述

单选按钮是一种可勾选的输入控件,当它与其他单选按钮关联时,一次只能选中其中一个。单选按钮必须分组在 radiogroup 中,以指示它们会影响同一个值。

html
<div role="radiogroup" aria-labelledby="legend25" id="radiogroup25">
  <p id="legend25">Ipsum and lorem?</p>
  <div>
    <span
      role="radio"
      aria-checked="false"
      tabindex="0"
      aria-labelledby="q25_radio1-label"
      data-value="True"></span>
    <label id="q25_radio1-label">True</label>
  </div>
  <div>
    <span
      role="radio"
      aria-checked="false"
      tabindex="0"
      aria-labelledby="q25_radio2-label"
      data-value="False"></span>
    <label id="q25_radio2-label">False</label>
  </div>
  <div>
    <span
      role="radio"
      aria-checked="true"
      tabindex="0"
      aria-labelledby="q25_radio3-label"
      data-value="huh?"></span>
    <label id="q25_radio3-label">What is the question?</label>
  </div>
</div>

role 属性仅添加语义; HTML radio 原生提供的所有功能都需要通过 JavaScript 和 HTML tabindex 属性来添加。

注意: ARIA 的第一条规则是,如果一个原生的 HTML 元素或属性具有您需要的语义和行为,请使用它,而不是重新利用一个元素并添加 ARIA。应改为使用原生的 HTML <input type="radio">(以及关联的 <label>),它本身就提供了所有必需的功能。

html
<fieldset>
  <legend>Ipsum and lorem?</legend>
  <div>
    <input type="radio" value="True" id="q25_radio1" name="q25" />
    <label for="q25_radio1">True</label>
  </div>
  <div>
    <input type="radio" value="False" id="q25_radio2" name="q25" />
    <label for="q25_radio2">False</label>
  </div>
  <div>
    <input type="radio" value="huh?" id="q25_radio3" name="q25" checked />
    <label for="q25_radio3">What is the question?</label>
  </div>
</fieldset>

原生的 HTML 单选按钮表单控件(<input type="radio">)有两种状态(“checked” 或 “not checked”)。同样,具有 role="radio" 的元素可以通过 aria-checked 属性暴露两种状态:true 表示选中状态,false 表示未选中状态。aria-checkedmixed 值不能用于单选按钮。

如果单选按钮被选中,则 radio 元素将 aria-checked 设置为 true。如果未选中,则将其设置为 false

每个单选按钮元素都具有 radio 角色。radio 角色应始终与其他关联的 radio 一起嵌套在 radiogroup 中。如果无法在标记中将单选按钮嵌套在 radio 组中,请使用非分组 radio 的 id 作为 radiogroup 元素上 aria-owns 属性的值,该值以空格分隔,以指示 radiogroup 与其 radio 成员的关系。

每个 radio 元素都可以通过其内容进行标记,或者由 aria-labelledby 引用的可见标签进行标记,或者通过 aria-label 指定标签。包含的 radiogroup 元素应通过 aria-labelledby 引用的可见标签进行标记,或通过 aria-label 指定标签。如果存在提供关于 radio 组或每个 radio 按钮的附加信息的元素,则这些元素应由 radiogroup 元素或 radio 元素使用 aria-describedby 属性进行引用。

由于 radio 是一个交互式控件;它必须是可聚焦的并且可以通过键盘访问。如果将角色应用于非聚焦元素,请使用 tabindex 属性进行更改。激活 radio 的预期键盘快捷键是 Space 键。当 radio 被选中时,使用 JavaScript 将 aria-checked 属性切换为 true,并确保组中的所有其他 radio 角色都设置为 aria-checked="false"

为了以编程方式指示必须从 radio 组中选择一个单选按钮,必须在 radiogroup 元素上指定值为 truearia-required 属性。不建议在单独的 ARIA 单选按钮上使用 aria-required 属性。

所有后代都是展示性的

某些类型的用户界面组件,当在平台辅助功能 API 中表示时,只能包含文本。辅助功能 API 无法表示 radio 中包含的语义元素。为了处理此限制,浏览器会自动将 presentation 角色应用于任何 radio 元素的所有后代元素,因为它是一个不支持语义子元素的角色。

例如,考虑以下包含标题的 radio 元素。

html
<div role="radio"><h6>name of my radio</h6></div>

由于 radio 的后代是呈现性的,因此以下代码等效:

html
<div role="radio"><h6 role="presentation">name of my radio</h6></div>

从辅助技术用户的角度来看,标题不存在,因为前面的代码片段在辅助功能树中等同于以下内容:

html
<div role="radio">name of my radio</div>

关联的 WAI-ARIA 角色、状态和属性

radiogroup 角色

单选按钮包含在或由具有 radiogroup 角色的元素拥有。如果无法在标记中将其嵌套在 radiogroup 中,则 radiogrouparia-owns 属性包含组中非嵌套单选按钮的 id 值。

aria-checked

aria-checked 的值定义了 radio 的状态。与 radio 元素一起使用时,该属性具有以下两种可能的值之一:

true

Radio 已选中。

false

Radio 未选中。

注意: 如果 role="radio" 用于一个不原生接受键盘焦点的元素,请使用 tabindex 属性。例如,<div><span>

键盘交互

Tab + Shift

将焦点移入和移出 radio 组。当焦点移入 radio 组,并且已选中某个 radio 按钮时,焦点将设置在已选中的按钮上。如果所有 radio 按钮都未选中,焦点将设置在组中的第一个 radio 按钮上。

空格

如果 radio 未被选中,则选中它。取消选中 radio 组中之前已选中的 radio 按钮。

右箭头键下箭头键

将焦点移至组中的下一个 radio 按钮并选中它,同时取消选中之前具有焦点的 radio 按钮。如果焦点在最后一个 radio 按钮上,焦点将移至第一个 radio 按钮。

左箭头键上箭头键

将焦点移至组中的上一个 radio 按钮并选中它,同时取消选中之前具有焦点的 radio 按钮。如果焦点在第一个 radio 按钮上,焦点将移至最后一个 radio 按钮。

工具栏中的 radios

由于箭头键用于在工具栏元素之间导航,而 Tab 键用于将焦点移入和移出工具栏,因此当 radio 组嵌套在工具栏中时,radio 组的键盘交互与非嵌套在工具栏中的 radio 组略有不同。有关更多信息,请参阅 radiogroup 键盘交互

必需的 JavaScript

onClick

处理对 radio 及其关联标签的鼠标点击,通过更改 aria-checked 属性的值来更改 radio 的状态,并改变 radio 的外观,使其对可见用户显示为已选中或未选中。

onKeyPress

处理用户按下 Space 键以更改 radio 的状态的情况,通过更改 aria-checked 属性的值来更改 radio 的状态,并改变 radio 的外观,使其对可见用户显示为已选中或未选中。

示例

以下示例使用 ARIA 来修改其他通用元素,使其暴露为单选按钮。CSS 和 JavaScript 用于在视觉上和编程上修改元素的选中或未选中状态。

HTML

html
<div role="radiogroup" aria-labelledby="legend" id="radiogroup">
  <p id="legend">
    Should you be using the <code>radio</code> role or
    <code>&lt;input type="radio"></code>?
  </p>
  <div>
    <span
      role="radio"
      aria-checked="true"
      tabindex="0"
      aria-labelledby="ariaLabel"
      data-value="True"></span>
    <label id="ariaLabel">ARIA role</label>
  </div>
  <div>
    <span
      role="radio"
      aria-checked="false"
      tabindex="0"
      aria-labelledby="htmllabel"
      data-value="False"></span>
    <label id="htmllabel">HTML <code>&lt;input type="radio"></code></label>
  </div>
</div>

CSS

css
[role="radio"] {
  padding: 5px;
}

[role="radio"][aria-checked="true"]::before {
  content: "(x)";
  font-family: monospace;
}

[role="radio"][aria-checked="false"]::before {
  content: "( )";
  font-family: monospace;
}

JavaScript

要将非语义 HTML 制作成单选按钮,需要大量的 JavaScript。

js
// initialize all the radio role elements

const radioGroups = document.querySelectorAll('[role="radiogroup"]');

for (const radioGroup of radioGroups) {
  const radios = radioGroup.querySelectorAll("[role=radio]");
  for (const radio of radios) {
    radio.addEventListener("keydown", handleKeydown);
    radio.addEventListener("click", handleClick);
  }
}

// handle mouse and touch events
function handleClick(event) {
  setChecked(this);
  event.stopPropagation();
  event.preventDefault();
}

// handle key presses
function handleKeydown(event) {
  switch (event.code) {
    case "Space":
    case "Enter":
      currentChecked();
      break;

    case "ArrowUp":
    case "ArrowLeft":
      previousRadioChecked();
      break;

    case "ArrowDown":
    case "ArrowRight":
      nextItemChecked();
      break;

    default:
      break;
  }
  event.stopPropagation();
  event.preventDefault();
}

// when a radio is selected, give it focus, set checked to true;
// ensure all other radios in radio group are not checked

function setChecked() {
  // uncheck all the radios in group
  // iterated through all the radios in radio group
  // eachRadio.tabIndex = -1;
  // eachRadio.setAttribute('aria-checked', 'false');
  // set the selected radio to checked
  // thisRadio.setAttribute('aria-checked', 'true');
  // thisRadio.tabIndex = 0;
  // thisRadio.focus();
  // set the value of the radioGroup to the value of the currently selected radio
}

如果使用了语义 HTML 元素,并且一组单选按钮中每个单选按钮的名称相同,则不需要 JavaScript(甚至不需要 CSS)。

html
<fieldset>
  <legend>
    Should you be using the <code>radio</code> role or
    <code>&lt;input type="radio"></code>?
  </legend>
  <div>
    <input type="radio" name="bestPractices" id="ariaLabel" value="True" />
    <label for="ariaLabel">ARIA role</label>
  </div>
  <div>
    <input type="radio" name="bestPractices" id="htmllabel" value="False" />
    <label for="htmllabel">HTML <code>&lt;input type="radio"></code></label>
  </div>
</fieldset>

最佳实践

ARIA 的第一条规则是:如果一个原生的 HTML 元素或属性具有您需要的语义和行为,请使用它,而不是重新利用一个元素并添加 ARIA 角色、状态或属性来使其可访问。因此,建议使用原生的 HTML 单选按钮表单控件,而不是通过 JavaScript 和 ARIA 来重新创建 radio 的功能。

另见