Skip to content Skip to sidebar Skip to footer

Best Way To Update Aria-expanded Attribute When Using :focus-within

I'm working on a combobox, and in doing so, I want to use the new :focus-within pseudo selector to manage displaying the expandable listbox that's associated with the combobox. :fo

Solution 1:

focus-within only has 84% browser coverage

For this reason that instantly makes your solution inaccessible as a lot of screen reader users still use JAWs with Internet Explorer.

Additionally you have the problem that while this works as a demo, in the real world an auto-complete list will be populated via AJAX or via a preloaded list that is filtered.

This means that the list will always be shown the second you focus the <input>, even when nothing has been typed into the combobox (which is not expected behaviour).

This is one of the few circumstances where relying solely on JavaScript is acceptable (with a fallback that the form can still be submitted without JavaScript).

Instead of trying to use :focus-within you can instead use JavaScript to toggle aria-expanded="true" when you return some suggestions and then use standard CSS3 selectors to show and hide the results.

The below example shows the CSS to achieve this. The + operator is the key, it is the Adjacent Sibling Combinator that selects the next sibling within a parent element.

CSS:.combobox-container div[aria-expanded="true"]+.list

For the example below I have made it so that once you type more than 1 character into the box it will change the aria-expanded attribute to true (and back again if the input is empty) - this makes it feel more like a 'real world' example.

Side note: You do not need to add a tabindex to the <ul>, the expected behaviour is to tab directly to the first suggested item, I have removed that in the example below.

//ignore this, this is my standard jQuery replacement for snippetsif(typeof $=="undefined"){!function(b,c,d,e,f){f=b['add'+e]
functioni(a,d,i){for(d=(a&&a.nodeType?[a]:''+a===a?b.querySelectorAll(a):c),i=d.length;i--;c.unshift.call(this,d[i]));}
$=function(a){return/^f/.test(typeof a)?/in/.test(b.readyState)?setTimeout(function(){$(a);},9):a():newi(a);};$[d]=i[d]={on:function(a,b){returnthis.each(function(c){f?c['add'+e](a,b,false):c.attachEvent('on'+a,b)})},off:function(a,b){returnthis.each(function(c){f?c['remove'+e](a,b):c.detachEvent('on'+a,b)})},each:function(a,b){for(var c=this,d=0,e=c.length;d<e;++d){a.call(b||c[d],c[d],d,c)}
return c},splice:c.splice}}(document,[],'prototype','EventListener');var props=['add','remove','toggle','has'],maps=['add','remove','toggle','contains'];props.forEach(function(prop,index){$.prototype[prop+'Class']=function(a){returnthis.each(function(b){if(a){b.classList[maps[index]](a);}});};});$.prototype.hasClass=function(a){returnthis[0].classList.contains(a);};}
$.prototype.find=function(selector){return $(selector,this);};$.prototype.parent=function(){return(this.length==1)?$(this[0].parentNode):[];};$.prototype.findWithin=function(a){console.log("THIS IS",this[0],a);returnthis[0].getElementsByClassName(a);};$.prototype.first=function(){return $(this[0]);};$.prototype.focus=function(){returnthis[0].focus();};$.prototype.css=function(a,b){if(typeof(a)==='object'){for(var prop in a){this.each(function(c){c.style[prop]=a[prop];});}
returnthis;}else{return b===[]._?this[0].style[a]:this.each(function(c){c.style[a]=b;});}};$.prototype.text=function(a){return a===[]._?this[0].textContent:this.each(function(b){b.textContent=a;});};$.prototype.html=function(a){return a===[]._?this[0].innerHTML:this.each(function(b){b.innerHTML=a;});};$.prototype.attr=function(a,b){return b===[]._?this[0].getAttribute(a):this.each(function(c){c.setAttribute(a,b);});};$.param=function(obj,prefix){var str=[];for(var p in obj){var k=prefix?prefix+"["+p+"]":p,v=obj[p];str.push(typeof v=="object"?$.param(v,k):encodeURIComponent(k)+"="+encodeURIComponent(v));}
return str.join("&");};$.prototype.append=function(a){returnthis.each(function(b){b.appendChild(a[0]);});};$.ajax=function(a,b,c,d){var xhr=newXMLHttpRequest();var type=(typeof(b)==='object')?1:0;var gp=['GET','POST'];xhr.open(gp[type],a,true);if(type==1){xhr.setRequestHeader("X-Requested-With","XMLHttpRequest");}
xhr.responseType=(typeof(c)==='string')?c:'';var cb=(!type)?b:c;xhr.onerror=function(){cb(this,true);};xhr.onreadystatechange=function(){if(this.readyState===4){if(this.status>=200&&this.status<400){cb(this,false);}else{cb(this,true);}}};if(type){xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');xhr.send($.param(b));}else{xhr.send();}
xhr=null;};

//only part of the demo, not for production use
$('input').on('keyup', function(e){
    if($(this)[0].value.length > 0){
        $('div[role=combobox]').attr('aria-expanded', true);
        return; 
    }
    $('div[role=combobox]').attr('aria-expanded', false);
    return;
});
.list {
  display: none;
}
.combobox-containerdiv[aria-expanded="true"]+.list{
  display: block;
  border:2px solid #333;
}
<sectionclass="combobox-container"><divrole="combobox"aria-expanded="false"aria-owns="listbox"aria-haspopup="listbox"><label> Foo
       <inputtype="text"aria-autocomplete="list"aria-controls="listbox" /></label></div><ulclass="list"id="listbox"role="listbox"aria-multiselectable="true"><!-- items for autocomplete. focusable anchors inside li tags. --><li><ahref="#">Javascript</a></li><li><ahref="#">HTML</a></li><li><ahref="#">CSS</a></li></ul></section>

Post a Comment for "Best Way To Update Aria-expanded Attribute When Using :focus-within"