Last updated: April 27, 2009
If you find any of the information useful, or if you find anything to be incomplete or inaccurate, please let me know.
This test page explores keyboard accessibility problems with accordion interfaces, looking specifically at jQuery UI’s Accordion interface widget. The supporting article for this test page can be found on my blog: jQuery UI accordion: enabling keyboard navigation.
The jQuery UI Accordion plugin applies tabindex="0"
to the element acting as the Accordion header. This is actually done for accessibility reasons; it should tell the browser to make the header element part of the tab order, making it more accessible by keyboard. However, it causes a problem in Firefox, where the Accordion cannot be navigated using the keyboard. The jQuery UI team are aware of this problem and intend to remove the tabindex="0"
set by the Accordion code.
To ensure accessibility, the Accordion header should be a normal focusable element, e.g. a hyperlink. The onclick
event handlers on such elements are sure to behave correctly under normal usage, so they can be used to trigger the Accordion behaviour. Unfortunately, the Accordion will currently not work correctly if the Accordion header is wrapped in an outer element, e.g. <h2><a class="ui-accordion-header"></a></h2>
.
Below are some example implementations to demonstrate the broken keyboard accessibility and some suggested solutions. Notes are included in the first section of each example, followed by the jQuery code and markup used.
NB: If the Accordion does not load correctly, it's likely that the jQuery files have not loaded. If this happens, either wait for a moment to give the files a chance to load, or try refreshing this page.
Due to a tabindex bug in Firefox, you can tab to each accordion header, but in this example, the element does not allow activation via keyboard, not even via the onclick event; you must use a link or a button for this purpose. Furthermore, the bug does not permit a user to tab to the link contained within the accordion header. You cannot tab backwards through the headers.
// Control (jQuery UI sets tabindex="0").
console.info("Control: #accordion1 .ui-accordion-header");
$("#accordion1")
.accordion({
header: ".ui-accordion-header"
});
$("#accordion1 .ui-accordion-header")
.each(function(){console.log(this)});
<ul id="accordion{N}">
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} notes</a></h3>
<div class="ui-accordion-content">
<h4>{TITLE}</h4>
{NOTES}
</div>
</li>
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} jQuery</a></h3>
<div class="ui-accordion-content">
<pre class="code"><code>{JQUERY}</code></pre>
</div>
</li>
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} markup</a></h3>
<div class="ui-accordion-content">
<pre class="code"><code>{MARKUP}</code></pre>
</div>
</li>
</ul>
This solution removes the tabindex attribute set by jQuery UI using this code:
$(".ui-accordion-header")
.attr("tabindex","");
// Blank tabindex (tabindex="").
console.info("Undo: #accordion2 .ui-accordion-header");
$("#accordion2")
.accordion({
header: ".ui-accordion-header"
});
$("#accordion2 .ui-accordion-header")
.each(function(){console.log(this)})
.attr("tabindex","");
console.log("tabindex attributes blanked:");
$("#accordion2 .ui-accordion-header")
.each(function(){console.log(this)});
<ul id="accordion{N}">
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} notes</a></h3>
<div class="ui-accordion-content">
<h4>{TITLE}</h4>
{NOTES}
</div>
</li>
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} jQuery</a></h3>
<div class="ui-accordion-content">
<pre class="code"><code>{JQUERY}</code></pre>
</div>
</li>
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} markup</a></h3>
<div class="ui-accordion-content">
<pre class="code"><code>{MARKUP}</code></pre>
</div>
</li>
</ul>
This solution counteracts the tabindex attribute set by jQuery UI using this code:
$(".ui-accordion-header")
.attr("tabindex",-1);
// Pseudo-focusable tabindex (tabindex="-1").
console.info("Redo: #accordion3 .ui-accordion-header");
$("#accordion3")
.accordion({
header: ".ui-accordion-header"
});
$("#accordion3 .ui-accordion-header")
.each(function(){console.log(this)})
.attr("tabindex",-1);
console.log("tabindex attributes set to -1:");
$("#accordion3 .ui-accordion-header")
.each(function(){console.log(this)});
<ul id="accordion{N}">
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} notes</a></h3>
<div class="ui-accordion-content">
<h4>{TITLE}</h4>
{NOTES}
</div>
</li>
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} jQuery</a></h3>
<div class="ui-accordion-content">
<pre class="code"><code>{JQUERY}</code></pre>
</div>
</li>
<li class="ui-accordion-group">
<h3 class="ui-accordion-header"><a href="#">Accordion {N} markup</a></h3>
<div class="ui-accordion-content">
<pre class="code"><code>{MARKUP}</code></pre>
</div>
</li>
</ul>
Use an actionable element as the accordion header in the first place and style it appropriately.
If it doesn't make sense to have the accordion header links in your markup, you could dynamically add the links to this example with JavaScript.
// Slightly better approach useing an actionable element as the accordion header.
console.info("Using anchors: #accordion4 .ui-accordion-header");
$("#accordion4")
.accordion({
header: ".ui-accordion-header"
});
$("#accordion4 .ui-accordion-header")
.each(function(){console.log(this)});
<ul id="accordion{N}">
<li class="ui-accordion-group">
<a href="#" class="ui-accordion-header">Accordion {N} notes</a>
<div class="ui-accordion-content">
<h4>{TITLE}</h4>
{NOTES}
</div>
</li>
<li class="ui-accordion-group">
<a href="#" class="ui-accordion-header">Accordion {N} jQuery</a>
<div class="ui-accordion-content">
<pre class="code"><code>{JQUERY}</code></pre>
</div>
</li>
<li class="ui-accordion-group">
<a href="#" class="ui-accordion-header">Accordion {N} markup</a>
<div class="ui-accordion-content">
<pre class="code"><code>{MARKUP}</code></pre>
</div>
</li>
</ul>
It makes sense to include headings for each accordion group. The problem with this is that you can end up with the same problem as before. The accordion will not work if we wrap the link acting as our accordion header inside another element; <h3><a class=".ui-accordion-header"> will not work. If we want to do this in our markup, we can dynamically add a link with JavaScript and use the tabindex fix as before.
Again, use an actionable element as the accordion header in the first place and style it appropriately, but this time start with clean markup and progressively enhance.
// Dynamically add accordion headers.
console.info("Dynamically added anchors: #accordion5 .ui-accordion-header");
$("#accordion5 li")
.addClass("ui-accordion-group");
$("#accordion5 .li-a h3:first-child")
.addClass("ui-accordion-header")
.wrapInner("<a href=\"#\"><\/a>")
.each(function() {
$(this).siblings().wrapAll("<div class=\"ui-accordion-content\"><\/div>");
});
$("#accordion5 .li-a .ui-accordion-content").ready(function(){
$("#accordion5 .li-b h3:first-child")
.addClass("ui-accordion-header")
.wrapInner("<a href=\"#\"><\/a>")
.each(function() {
$(this).siblings().wrapAll("<div class=\"ui-accordion-content\"><\/div>");
});
});
$("#accordion5 .li-b .ui-accordion-content").ready(function(){
$("#accordion5 .li-c h3:first-child")
.addClass("ui-accordion-header")
.wrapInner("<a href=\"#\"><\/a>")
.each(function() {
$(this).siblings().wrapAll("<div class=\"ui-accordion-content\"><\/div>");
});
});
$("#accordion5 .li-c .ui-accordion-content").ready(function(){
$("#accordion5")
.accordion({
header: ".ui-accordion-header"
});
});
$("#accordion5 .ui-accordion-header")
.attr("tabindex","")
.each(function(){console.log(this)});
<ul id="accordion{N}">
<li class="li-a">
<h3>Accordion {N} notes</h3>
<h4>{TITLE}</h4>
{NOTES}
</li>
<li class="li-b">
<h3>Accordion {N} jQuery</h3>
<pre class="code"><code>{JQUERY}</code></pre>
</li>
<li class="li-c">
<h3>Accordion {N} markup</h3>
<pre class="code"><code>{MARKUP}</code></pre>
</li>
</ul>