多级列表的语义正确层次结构是什么?
What is semantically correct hierarchy for multi-level lists?
我将有 2 个列表:带有单选按钮和复选框(多级)
我目前的 html 是:
<label>
<input type="radio" name="group" checked={checked} onChange={()=>callback(value)}/>
{label}
</label>
<label>
<input type="radio" name="subgroup" checked={checked} onChange={()=>callback(value)}/>
{label}
</label>
<label>
<input type="radio" name="subgroup" checked={checked} onChange={()=>callback(value)}/>
{label}
</label>
我的 subgroup
没有放在第一个标签内可以吗?我见过很多不同的例子,但我想知道哪个在语义上是正确的?
回答你的第一个问题,<label>
元素一次只能标记一个表单元素,所以你不应该在一个 <label>
.[=22= 中放置多个表单元素。 ]
对于无线电组,由于您只能 select 所有 sub-item 中的一个 sub-item,因此最语义化和最易于理解的方式是将其作为<select>
个元素,每个 top-level 个分组有 <optgroup>
个元素,每个 sub-item 个分组有 <option>
个元素。这就是这个元素的用途。
<label for="dino-select">Choose a dinosaur:</label>
<select id="dino-select">
<optgroup label="Theropods">
<option>Tyrannosaurus</option>
<option>Velociraptor</option>
<option>Deinonychus</option>
</optgroup>
<optgroup label="Sauropods">
<option>Diplodocus</option>
<option>Saltasaurus</option>
<option>Apatosaurus</option>
</optgroup>
</select>
对于需要能够select多个东西的情况,可以添加multiple
属性:
<label for="dino-select">Choose one or more dinosaurs:</label>
<select id="dino-select" multiple>
<optgroup label="Theropods">
<option>Tyrannosaurus</option>
<option>Velociraptor</option>
<option>Deinonychus</option>
</optgroup>
<optgroup label="Sauropods">
<option>Diplodocus</option>
<option>Saltasaurus</option>
<option>Apatosaurus</option>
</optgroup>
</select>
如果您不喜欢使用 <select multiple />
元素(有些用户不知道如何使用它们,因为它们不常见),您可以使用嵌套的复选框列表。
务必以可访问的方式在语义上表示哪些项目属于其他项目,并包含 javascript 以添加必要的功能。
关于预期行为的注释:
- 如果一个父项被 selected,所有子项都应该被 selected
- 如果所有子项都变成 selected,则父项应该变成 selected
- 如果所有子项都被删除select,父项也应该被删除select
- 如果父项的某些子项被 select 编辑而其他子项未被编辑,则父项应处于不确定状态
这是一种方法:
const setInputState = (el, state) => {
if (state === 'indeterminate') {
el.indeterminate = true
} else {
el.indeterminate = false
el.checked = state
}
}
const updateOwned = (el) => {
if (el.hasAttribute('data-children')) {
let state = el.checked
el.getAttribute('data-children').split(' ').forEach(id => {
let owned = document.getElementById(id)
setInputState(owned, state)
updateOwned(owned)
})
}
}
const updateOwner = (el) => {
if (el.hasAttribute('data-parent')) {
let owner = document.getElementById(el.getAttribute('data-parent'))
let states = []
let collectiveState
owner.getAttribute('data-children').split(' ').every(id => {
let owned = document.getElementById(id)
let state = owned.indeterminate === true ? 'indeterminate' : owned.checked
if (states.length > 0 && states.indexOf(state) === -1) {
collectiveState = 'indeterminate'
return false
} else {
states.push(state)
return true
}
})
collectiveState = collectiveState || states[0]
setInputState(owner, collectiveState)
updateOwner(owner)
}
}
document.querySelectorAll('.nested-multiselect').forEach(multiselect => {
multiselect.querySelectorAll('input[type="checkbox"][data-children], input[type="checkbox"][data-parent]').forEach(input => {
input.addEventListener('change', event => {
updateOwned(event.currentTarget)
updateOwner(event.currentTarget)
})
})
})
body {
padding: 2rem;
}
label {
display: block;
}
label span:before {
content: ' ';
}
fieldset fieldset {
border: none;
padding: 0 0 0 1ch;
}
<fieldset class="nested-multiselect">
<legend>Categories </legend>
<label id="label-fruit">
<input id="fruit" type="checkbox" name="categories" value="fruit" aria-owns="subcategories-fruit" data-children="apple orange banana"/><span>fruit</span>
</label>
<fieldset id="subcategories-fruit" aria-label="fruit subcategories">
<label id="label-apple">
<input id="apple" type="checkbox" name="categories" value="apple" aria-owns="subcategories-apple" data-parent="fruit" data-children="gala macintosh honeycrisp"/><span>apple</span>
</label>
<fieldset id="subcategories-apple" aria-label="apple subcategories">
<label id="label-gala">
<input id="gala" type="checkbox" name="categories" value="gala" data-parent="apple"/><span>gala</span>
</label>
<label id="label-macintosh">
<input id="macintosh" type="checkbox" name="categories" value="macintosh" data-parent="apple"/><span>macintosh</span>
</label>
<label id="label-honeycrisp">
<input id="honeycrisp" type="checkbox" name="categories" value="honeycrisp" data-parent="apple"/><span>honeycrisp</span>
</label>
</fieldset>
<label id="label-orange">
<input id="orange" type="checkbox" name="categories" value="orange" data-parent="fruit"/><span>orange</span>
</label>
<label id="label-banana">
<input id="banana" type="checkbox" name="categories" value="banana" data-parent="fruit"/><span>banana</span>
</label>
</fieldset>
<label id="label-vegetables">
<input id="vegetables" type="checkbox" name="categories" value="vegetables" aria-owns="subcategories-vegetables" data-children="squash peas leek"/><span>vegetables</span>
</label>
<fieldset id="subcategories-vegetables" aria-label="vegetables subcategories">
<label id="label-squash">
<input id="squash" type="checkbox" name="categories" value="squash" data-parent="vegetables"/><span>squash</span>
</label>
<label id="label-peas">
<input id="peas" type="checkbox" name="categories" value="peas" data-parent="vegetables"/><span>peas</span>
</label>
<label id="label-leek">
<input id="leek" type="checkbox" name="categories" value="leek" data-parent="vegetables"/><span>leek</span>
</label>
</fieldset>
</fieldset>
我将有 2 个列表:带有单选按钮和复选框(多级) 我目前的 html 是:
<label>
<input type="radio" name="group" checked={checked} onChange={()=>callback(value)}/>
{label}
</label>
<label>
<input type="radio" name="subgroup" checked={checked} onChange={()=>callback(value)}/>
{label}
</label>
<label>
<input type="radio" name="subgroup" checked={checked} onChange={()=>callback(value)}/>
{label}
</label>
我的 subgroup
没有放在第一个标签内可以吗?我见过很多不同的例子,但我想知道哪个在语义上是正确的?
回答你的第一个问题,<label>
元素一次只能标记一个表单元素,所以你不应该在一个 <label>
.[=22= 中放置多个表单元素。 ]
对于无线电组,由于您只能 select 所有 sub-item 中的一个 sub-item,因此最语义化和最易于理解的方式是将其作为<select>
个元素,每个 top-level 个分组有 <optgroup>
个元素,每个 sub-item 个分组有 <option>
个元素。这就是这个元素的用途。
<label for="dino-select">Choose a dinosaur:</label>
<select id="dino-select">
<optgroup label="Theropods">
<option>Tyrannosaurus</option>
<option>Velociraptor</option>
<option>Deinonychus</option>
</optgroup>
<optgroup label="Sauropods">
<option>Diplodocus</option>
<option>Saltasaurus</option>
<option>Apatosaurus</option>
</optgroup>
</select>
对于需要能够select多个东西的情况,可以添加multiple
属性:
<label for="dino-select">Choose one or more dinosaurs:</label>
<select id="dino-select" multiple>
<optgroup label="Theropods">
<option>Tyrannosaurus</option>
<option>Velociraptor</option>
<option>Deinonychus</option>
</optgroup>
<optgroup label="Sauropods">
<option>Diplodocus</option>
<option>Saltasaurus</option>
<option>Apatosaurus</option>
</optgroup>
</select>
如果您不喜欢使用 <select multiple />
元素(有些用户不知道如何使用它们,因为它们不常见),您可以使用嵌套的复选框列表。
务必以可访问的方式在语义上表示哪些项目属于其他项目,并包含 javascript 以添加必要的功能。
关于预期行为的注释:
- 如果一个父项被 selected,所有子项都应该被 selected
- 如果所有子项都变成 selected,则父项应该变成 selected
- 如果所有子项都被删除select,父项也应该被删除select
- 如果父项的某些子项被 select 编辑而其他子项未被编辑,则父项应处于不确定状态
这是一种方法:
const setInputState = (el, state) => {
if (state === 'indeterminate') {
el.indeterminate = true
} else {
el.indeterminate = false
el.checked = state
}
}
const updateOwned = (el) => {
if (el.hasAttribute('data-children')) {
let state = el.checked
el.getAttribute('data-children').split(' ').forEach(id => {
let owned = document.getElementById(id)
setInputState(owned, state)
updateOwned(owned)
})
}
}
const updateOwner = (el) => {
if (el.hasAttribute('data-parent')) {
let owner = document.getElementById(el.getAttribute('data-parent'))
let states = []
let collectiveState
owner.getAttribute('data-children').split(' ').every(id => {
let owned = document.getElementById(id)
let state = owned.indeterminate === true ? 'indeterminate' : owned.checked
if (states.length > 0 && states.indexOf(state) === -1) {
collectiveState = 'indeterminate'
return false
} else {
states.push(state)
return true
}
})
collectiveState = collectiveState || states[0]
setInputState(owner, collectiveState)
updateOwner(owner)
}
}
document.querySelectorAll('.nested-multiselect').forEach(multiselect => {
multiselect.querySelectorAll('input[type="checkbox"][data-children], input[type="checkbox"][data-parent]').forEach(input => {
input.addEventListener('change', event => {
updateOwned(event.currentTarget)
updateOwner(event.currentTarget)
})
})
})
body {
padding: 2rem;
}
label {
display: block;
}
label span:before {
content: ' ';
}
fieldset fieldset {
border: none;
padding: 0 0 0 1ch;
}
<fieldset class="nested-multiselect">
<legend>Categories </legend>
<label id="label-fruit">
<input id="fruit" type="checkbox" name="categories" value="fruit" aria-owns="subcategories-fruit" data-children="apple orange banana"/><span>fruit</span>
</label>
<fieldset id="subcategories-fruit" aria-label="fruit subcategories">
<label id="label-apple">
<input id="apple" type="checkbox" name="categories" value="apple" aria-owns="subcategories-apple" data-parent="fruit" data-children="gala macintosh honeycrisp"/><span>apple</span>
</label>
<fieldset id="subcategories-apple" aria-label="apple subcategories">
<label id="label-gala">
<input id="gala" type="checkbox" name="categories" value="gala" data-parent="apple"/><span>gala</span>
</label>
<label id="label-macintosh">
<input id="macintosh" type="checkbox" name="categories" value="macintosh" data-parent="apple"/><span>macintosh</span>
</label>
<label id="label-honeycrisp">
<input id="honeycrisp" type="checkbox" name="categories" value="honeycrisp" data-parent="apple"/><span>honeycrisp</span>
</label>
</fieldset>
<label id="label-orange">
<input id="orange" type="checkbox" name="categories" value="orange" data-parent="fruit"/><span>orange</span>
</label>
<label id="label-banana">
<input id="banana" type="checkbox" name="categories" value="banana" data-parent="fruit"/><span>banana</span>
</label>
</fieldset>
<label id="label-vegetables">
<input id="vegetables" type="checkbox" name="categories" value="vegetables" aria-owns="subcategories-vegetables" data-children="squash peas leek"/><span>vegetables</span>
</label>
<fieldset id="subcategories-vegetables" aria-label="vegetables subcategories">
<label id="label-squash">
<input id="squash" type="checkbox" name="categories" value="squash" data-parent="vegetables"/><span>squash</span>
</label>
<label id="label-peas">
<input id="peas" type="checkbox" name="categories" value="peas" data-parent="vegetables"/><span>peas</span>
</label>
<label id="label-leek">
<input id="leek" type="checkbox" name="categories" value="leek" data-parent="vegetables"/><span>leek</span>
</label>
</fieldset>
</fieldset>