列表不仅仅包含 <li> 元素和脚本支持元素(<script> 和 <template>)
Lists do not contain only <li> elements and script supporting elements (<script> and <template>)
我正在 Google chrome light house 中测试我的网页的可访问性并收到以下错误。我了解正确构建列表以帮助提高网页的可访问性的重要性。我想知道为什么当我的列表结构正确时会出现以下错误。
HTML 代码
<div>
<ul class="oss-tabs custom-tab">
<li class="tab-item" [ngClass]="{active: tab.type === currentTab}"
*ngFor="let tab of tabs"
(click)="switchTab(tab.type)" role="button"
id="{{tab.title}}">{{tab.title}}</li>
</ul>
</div>
如果您指定 role="button"
它不再是列表元素而是 <button>
。而且您的列表中只能包含 <li>
。
更好的解决方案是以下标记:
<li id="{{tab.title}}" class="tab-item" [ngClass]="{active: tab.type === currentTab}" *ngFor="let tab of tabs" >
<button type="button" (click)="switchTab(tab.type)">{{tab.title}}</button>
</li>
这更好的原因:通过添加 role="button"
你说这个元素是一个按钮,但我不确定这是否也增加了所有可访问性的好处。就像你可以用键盘切换到它一样,你可以点击回车来触发按钮点击,类似这样的东西。
另外不要忘记为列表中的按钮指定 type="button"
,否则它将是一个提交按钮(这对于这个地方来说是错误的)
您可以按照自己的方式设置按钮的样式,因此样式不是此处不使用按钮元素的借口。
选项卡的相关 WAI-ARIA 属性
有aria
attributes that are designed for tabs.
您需要担心的主要是 role="tablist"
(在选项卡容器上)、role="tab"
(对于选项卡控件)和 role="tabpanel"
(对于实际选项卡内容) .
这会向一些辅助技术发出信号以切换到选项卡模式,以便您可以使用箭头键等进行导航(尽管您仍然需要通过 JavaScript 通过箭头键等添加导航,因为大多数屏幕阅读器不会自动执行此操作),它还使关联清晰等
W3C have a great example of how to set up tabs,以下是您如何调整 mark-up 以匹配该模式。
<div>
<ul class="oss-tabs custom-tab" role="tablist">
<li class="tab-item" [ngClass]="{active: tab.type === currentTab}"
*ngFor="let tab of tabs"
(click)="switchTab(tab.type)"
id="{{tab.title}}"
role="tab">{{tab.title}}</li>
</ul>
</div>
制作制表符的更好方法
上面的缺点是你需要自己管理焦点状态等等
更好的方法是让列表项具有演示性,因为 tablist
会用 role="tab"
查找 children,并且不需要宣布列表项的不必要噪音。
Inclusive components did a great writeup on creating accessible tabbed interfaces,他们推荐的mark-up的例子如下:-
<div class="tabbed">
<ul role="tablist">
<li role="presentation">
<a href="#section1" role="tab" id="tab1" aria-selected="true">Section 1</a>
</li>
<li role="presentation">
<a href="#section2" role="tab" id="tab2" tabindex="-1">Section 2</a>
</li>
</ul>
<section id="section1" role="tabpanel" tabindex="-1" aria-labelledby="tab1">
<h2>Section 1</h2>
<p>Section 1 text</p>
</section>
<section id="section2" role="tabpanel" tabindex="-1" aria-labelledby="tab2" hidden="">
<h2>Section 2</h2>
<p>Section 2 text</p>
</section>
</div>
包含组件的完整示例
下面是包含组件的示例的完整工作版本。
这与上面显示的示例 HTML 没有区别,只是所有 aria
属性、roles
等都由 JavaScript 在下面的例子中。
示例中的注意事项
使用aria-selected
表示当前选项卡
使用锚而不是按钮 链接到具有相应 ID 的相关部分(语义正确并且是无 JS 的一个很好的后备)。
键盘控制 - 添加箭头键以交换选项卡。
tab 顺序 - 如果您按 Tab 并且相应的 tabpanel
具有可聚焦的内容,它会正确地跳转到该内容并且不是下一个标签。
(function() {
// Get relevant elements and collections
const tabbed = document.querySelector('.tabbed');
const tablist = tabbed.querySelector('ul');
const tabs = tablist.querySelectorAll('a');
const panels = tabbed.querySelectorAll('[id^="section"]');
// The tab switching function
const switchTab = (oldTab, newTab) => {
newTab.focus();
// Make the active tab focusable by the user (Tab key)
newTab.removeAttribute('tabindex');
// Set the selected state
newTab.setAttribute('aria-selected', 'true');
oldTab.removeAttribute('aria-selected');
oldTab.setAttribute('tabindex', '-1');
// Get the indices of the new and old tabs to find the correct
// tab panels to show and hide
let index = Array.prototype.indexOf.call(tabs, newTab);
let oldIndex = Array.prototype.indexOf.call(tabs, oldTab);
panels[oldIndex].hidden = true;
panels[index].hidden = false;
}
// Add the tablist role to the first <ul> in the .tabbed container
tablist.setAttribute('role', 'tablist');
// Add semantics are remove user focusability for each tab
Array.prototype.forEach.call(tabs, (tab, i) => {
tab.setAttribute('role', 'tab');
tab.setAttribute('id', 'tab' + (i + 1));
tab.setAttribute('tabindex', '-1');
tab.parentNode.setAttribute('role', 'presentation');
// Handle clicking of tabs for mouse users
tab.addEventListener('click', e => {
e.preventDefault();
let currentTab = tablist.querySelector('[aria-selected]');
if (e.currentTarget !== currentTab) {
switchTab(currentTab, e.currentTarget);
}
});
// Handle keydown events for keyboard users
tab.addEventListener('keydown', e => {
// Get the index of the current tab in the tabs node list
let index = Array.prototype.indexOf.call(tabs, e.currentTarget);
// Work out which key the user is pressing and
// Calculate the new tab's index where appropriate
let dir = e.which === 37 ? index - 1 : e.which === 39 ? index + 1 : e.which === 40 ? 'down' : null;
if (dir !== null) {
e.preventDefault();
// If the down key is pressed, move focus to the open panel,
// otherwise switch to the adjacent tab
dir === 'down' ? panels[i].focus() : tabs[dir] ? switchTab(e.currentTarget, tabs[dir]) : void 0;
}
});
});
// Add tab panel semantics and hide them all
Array.prototype.forEach.call(panels, (panel, i) => {
panel.setAttribute('role', 'tabpanel');
panel.setAttribute('tabindex', '-1');
let id = panel.getAttribute('id');
panel.setAttribute('aria-labelledby', tabs[i].id);
panel.hidden = true;
});
// Initially activate the first tab and reveal the first tab panel
tabs[0].removeAttribute('tabindex');
tabs[0].setAttribute('aria-selected', 'true');
panels[0].hidden = false;
})();
body {
max-width: 40rem;
padding: 0 1rem;
font-size: 125%;
line-height: 1.5;
margin: 1.5rem auto;
font-family: Arial, sans-serif;
}
* {
color: inherit;
margin: 0;
}
[role="tablist"] {
padding: 0;
}
[role="tablist"] li, [role="tablist"] a {
display: inline-block;
}
[role="tablist"] a {
text-decoration: none;
padding: 0.5rem 1em;
}
[role="tablist"] [aria-selected] {
border: 2px solid;
background: #fff;
border-bottom: 0;
position: relative;
top: 2px;
}
[role="tabpanel"] {
border: 2px solid;
padding: 1.5rem;
}
[role="tabpanel"] * + * {
margin-top: 0.75rem;
}
*:focus {
outline: none;
box-shadow: inset 0 0 0 4px lightBlue;
}
@media (max-width: 550px) {
[role="tablist"] li, [role="tablist"] a {
display: block;
position: static;
}
[role="tablist"] a {
border: 2px solid #222 !important;
}
[role="tablist"] li + li a {
border-top: 0 !important;
}
[role="tablist"] [aria-selected] {
position: static;
}
[role="tablist"] [aria-selected]::after {
content: '[=13=]20⬅';
}
[role="tabpanel"] {
border-top: 0;
}
}
<div class="tabbed">
<ul>
<li>
<a href="#section1">Section 1</a>
</li>
<li>
<a href="#section2">Section 2</a>
</li>
<li>
<a href="#section3">Section 3</a>
</li>
<li>
<a href="#section4">Section 4</a>
</li>
</ul>
<section id="section1">
<h2>Section 1</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</section>
<section id="section2">
<h2>Section 2</h2>
<p>Nullam at diam nec arcu suscipit auctor non a erat. Sed et magna semper, eleifend magna non, facilisis nisl. Proin et est et lorem dictum finibus ut nec turpis. Aenean nisi tortor, euismod a mauris a, mattis scelerisque tortor. Sed dolor risus, varius a nibh id, condimentum lacinia est. In lacinia cursus odio a aliquam. Curabitur tortor magna, laoreet ut rhoncus at, sodales consequat tellus.</p>
</section>
<section id="section3">
<h2>Section 3</h2>
<p>Phasellus ac tristique orci. Nulla maximus <a href="">justo nec dignissim consequat</a>. Sed vehicula diam sit amet mi efficitur vehicula in in nisl. Aliquam erat volutpat. Suspendisse lorem turpis, accumsan consequat consectetur gravida, <a href="#">pellentesque ac ante</a>. Aliquam in commodo ligula, sit amet mollis neque. Vestibulum at facilisis massa.</p>
</section>
<section id="section4">
<h2>Section 4</h2>
<p>Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. </p>
</section>
</div>
我正在 Google chrome light house 中测试我的网页的可访问性并收到以下错误。我了解正确构建列表以帮助提高网页的可访问性的重要性。我想知道为什么当我的列表结构正确时会出现以下错误。
HTML 代码
<div>
<ul class="oss-tabs custom-tab">
<li class="tab-item" [ngClass]="{active: tab.type === currentTab}"
*ngFor="let tab of tabs"
(click)="switchTab(tab.type)" role="button"
id="{{tab.title}}">{{tab.title}}</li>
</ul>
</div>
如果您指定 role="button"
它不再是列表元素而是 <button>
。而且您的列表中只能包含 <li>
。
更好的解决方案是以下标记:
<li id="{{tab.title}}" class="tab-item" [ngClass]="{active: tab.type === currentTab}" *ngFor="let tab of tabs" >
<button type="button" (click)="switchTab(tab.type)">{{tab.title}}</button>
</li>
这更好的原因:通过添加 role="button"
你说这个元素是一个按钮,但我不确定这是否也增加了所有可访问性的好处。就像你可以用键盘切换到它一样,你可以点击回车来触发按钮点击,类似这样的东西。
另外不要忘记为列表中的按钮指定 type="button"
,否则它将是一个提交按钮(这对于这个地方来说是错误的)
您可以按照自己的方式设置按钮的样式,因此样式不是此处不使用按钮元素的借口。
选项卡的相关 WAI-ARIA 属性
有aria
attributes that are designed for tabs.
您需要担心的主要是 role="tablist"
(在选项卡容器上)、role="tab"
(对于选项卡控件)和 role="tabpanel"
(对于实际选项卡内容) .
这会向一些辅助技术发出信号以切换到选项卡模式,以便您可以使用箭头键等进行导航(尽管您仍然需要通过 JavaScript 通过箭头键等添加导航,因为大多数屏幕阅读器不会自动执行此操作),它还使关联清晰等
W3C have a great example of how to set up tabs,以下是您如何调整 mark-up 以匹配该模式。
<div>
<ul class="oss-tabs custom-tab" role="tablist">
<li class="tab-item" [ngClass]="{active: tab.type === currentTab}"
*ngFor="let tab of tabs"
(click)="switchTab(tab.type)"
id="{{tab.title}}"
role="tab">{{tab.title}}</li>
</ul>
</div>
制作制表符的更好方法
上面的缺点是你需要自己管理焦点状态等等
更好的方法是让列表项具有演示性,因为 tablist
会用 role="tab"
查找 children,并且不需要宣布列表项的不必要噪音。
Inclusive components did a great writeup on creating accessible tabbed interfaces,他们推荐的mark-up的例子如下:-
<div class="tabbed">
<ul role="tablist">
<li role="presentation">
<a href="#section1" role="tab" id="tab1" aria-selected="true">Section 1</a>
</li>
<li role="presentation">
<a href="#section2" role="tab" id="tab2" tabindex="-1">Section 2</a>
</li>
</ul>
<section id="section1" role="tabpanel" tabindex="-1" aria-labelledby="tab1">
<h2>Section 1</h2>
<p>Section 1 text</p>
</section>
<section id="section2" role="tabpanel" tabindex="-1" aria-labelledby="tab2" hidden="">
<h2>Section 2</h2>
<p>Section 2 text</p>
</section>
</div>
包含组件的完整示例
下面是包含组件的示例的完整工作版本。
这与上面显示的示例 HTML 没有区别,只是所有 aria
属性、roles
等都由 JavaScript 在下面的例子中。
示例中的注意事项
使用aria-selected
表示当前选项卡
使用锚而不是按钮 链接到具有相应 ID 的相关部分(语义正确并且是无 JS 的一个很好的后备)。
键盘控制 - 添加箭头键以交换选项卡。
tab 顺序 - 如果您按 Tab 并且相应的 tabpanel
具有可聚焦的内容,它会正确地跳转到该内容并且不是下一个标签。
(function() {
// Get relevant elements and collections
const tabbed = document.querySelector('.tabbed');
const tablist = tabbed.querySelector('ul');
const tabs = tablist.querySelectorAll('a');
const panels = tabbed.querySelectorAll('[id^="section"]');
// The tab switching function
const switchTab = (oldTab, newTab) => {
newTab.focus();
// Make the active tab focusable by the user (Tab key)
newTab.removeAttribute('tabindex');
// Set the selected state
newTab.setAttribute('aria-selected', 'true');
oldTab.removeAttribute('aria-selected');
oldTab.setAttribute('tabindex', '-1');
// Get the indices of the new and old tabs to find the correct
// tab panels to show and hide
let index = Array.prototype.indexOf.call(tabs, newTab);
let oldIndex = Array.prototype.indexOf.call(tabs, oldTab);
panels[oldIndex].hidden = true;
panels[index].hidden = false;
}
// Add the tablist role to the first <ul> in the .tabbed container
tablist.setAttribute('role', 'tablist');
// Add semantics are remove user focusability for each tab
Array.prototype.forEach.call(tabs, (tab, i) => {
tab.setAttribute('role', 'tab');
tab.setAttribute('id', 'tab' + (i + 1));
tab.setAttribute('tabindex', '-1');
tab.parentNode.setAttribute('role', 'presentation');
// Handle clicking of tabs for mouse users
tab.addEventListener('click', e => {
e.preventDefault();
let currentTab = tablist.querySelector('[aria-selected]');
if (e.currentTarget !== currentTab) {
switchTab(currentTab, e.currentTarget);
}
});
// Handle keydown events for keyboard users
tab.addEventListener('keydown', e => {
// Get the index of the current tab in the tabs node list
let index = Array.prototype.indexOf.call(tabs, e.currentTarget);
// Work out which key the user is pressing and
// Calculate the new tab's index where appropriate
let dir = e.which === 37 ? index - 1 : e.which === 39 ? index + 1 : e.which === 40 ? 'down' : null;
if (dir !== null) {
e.preventDefault();
// If the down key is pressed, move focus to the open panel,
// otherwise switch to the adjacent tab
dir === 'down' ? panels[i].focus() : tabs[dir] ? switchTab(e.currentTarget, tabs[dir]) : void 0;
}
});
});
// Add tab panel semantics and hide them all
Array.prototype.forEach.call(panels, (panel, i) => {
panel.setAttribute('role', 'tabpanel');
panel.setAttribute('tabindex', '-1');
let id = panel.getAttribute('id');
panel.setAttribute('aria-labelledby', tabs[i].id);
panel.hidden = true;
});
// Initially activate the first tab and reveal the first tab panel
tabs[0].removeAttribute('tabindex');
tabs[0].setAttribute('aria-selected', 'true');
panels[0].hidden = false;
})();
body {
max-width: 40rem;
padding: 0 1rem;
font-size: 125%;
line-height: 1.5;
margin: 1.5rem auto;
font-family: Arial, sans-serif;
}
* {
color: inherit;
margin: 0;
}
[role="tablist"] {
padding: 0;
}
[role="tablist"] li, [role="tablist"] a {
display: inline-block;
}
[role="tablist"] a {
text-decoration: none;
padding: 0.5rem 1em;
}
[role="tablist"] [aria-selected] {
border: 2px solid;
background: #fff;
border-bottom: 0;
position: relative;
top: 2px;
}
[role="tabpanel"] {
border: 2px solid;
padding: 1.5rem;
}
[role="tabpanel"] * + * {
margin-top: 0.75rem;
}
*:focus {
outline: none;
box-shadow: inset 0 0 0 4px lightBlue;
}
@media (max-width: 550px) {
[role="tablist"] li, [role="tablist"] a {
display: block;
position: static;
}
[role="tablist"] a {
border: 2px solid #222 !important;
}
[role="tablist"] li + li a {
border-top: 0 !important;
}
[role="tablist"] [aria-selected] {
position: static;
}
[role="tablist"] [aria-selected]::after {
content: '[=13=]20⬅';
}
[role="tabpanel"] {
border-top: 0;
}
}
<div class="tabbed">
<ul>
<li>
<a href="#section1">Section 1</a>
</li>
<li>
<a href="#section2">Section 2</a>
</li>
<li>
<a href="#section3">Section 3</a>
</li>
<li>
<a href="#section4">Section 4</a>
</li>
</ul>
<section id="section1">
<h2>Section 1</h2>
<p>Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. <a href="#">Nam luctus</a>, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan.</p>
</section>
<section id="section2">
<h2>Section 2</h2>
<p>Nullam at diam nec arcu suscipit auctor non a erat. Sed et magna semper, eleifend magna non, facilisis nisl. Proin et est et lorem dictum finibus ut nec turpis. Aenean nisi tortor, euismod a mauris a, mattis scelerisque tortor. Sed dolor risus, varius a nibh id, condimentum lacinia est. In lacinia cursus odio a aliquam. Curabitur tortor magna, laoreet ut rhoncus at, sodales consequat tellus.</p>
</section>
<section id="section3">
<h2>Section 3</h2>
<p>Phasellus ac tristique orci. Nulla maximus <a href="">justo nec dignissim consequat</a>. Sed vehicula diam sit amet mi efficitur vehicula in in nisl. Aliquam erat volutpat. Suspendisse lorem turpis, accumsan consequat consectetur gravida, <a href="#">pellentesque ac ante</a>. Aliquam in commodo ligula, sit amet mollis neque. Vestibulum at facilisis massa.</p>
</section>
<section id="section4">
<h2>Section 4</h2>
<p>Nam luctus, enim in interdum condimentum, nisl diam iaculis lorem, vel volutpat mi leo sit amet lectus. Praesent non odio bibendum magna bibendum accumsan. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam euismod, tortor nec pharetra ultricies, ante erat imperdiet velit, nec laoreet enim lacus a velit. </p>
</section>
</div>