Jekyll2024-01-08T15:32:06+00:00https://scottohara.me/feed.xmlScott O’Hara - Accessibility engineer, UX developer and designerScott O'Hara is a designer and developer based outside of Boston MA. He writes code and cares a ton about building great, accessible user experiences for everyone.
Scott O'HaraThe search element2023-03-24T00:00:00+00:002023-03-24T00:00:00+00:00https://scottohara.me/blog/2023/03/24/search-element<p>Today, March 24th 2023, the HTML specification added a new grouping content element. The <a href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-search-element"><code class="language-plaintext highlighter-rouge">search</code> element</a>.</p>
<div role="note">
<p>Please be aware that this element landing in the HTML spec today <strong>does not mean it is available in browsers today</strong>. Issues have been filed to implement the <code>search</code> element in the major browsers, including the necessary accessibility mappings. Keep this in mind when you add this new element to your pages.</p>
<p>Support update: Safari 17, Firefox 118 and Chrome/Edge 118 support the `search` element mapping to the implicit `search` landmark role. Safari 17 released mid September, Firefox early October and Chromium browsers will update to 118 during October, as well.</p>
</div>
<p>The impetus for this addition stems from the <a href="https://github.com/whatwg/html/issues/5811">“Consider creating an HTML search element” issue</a>, filed on the HTML spec in late 2020, where the goal was to create feature parity between ARIA and HTML.</p>
<p>ARIA defines <a href="https://www.w3.org/TR/wai-aria-1.2/#landmark_roles">8 landmark roles</a> to help authors structure their web pages. For every landmark role, except for <code class="language-plaintext highlighter-rouge">search</code>, a native HTML element could be used - under certain conditions - to expose these landmark roles. BTW, I’ve written a few things about landmarks in the past. Here’s but one of them <a href="https://www.scottohara.me/blog/2018/03/03/landmarks.html">Accessible Landmarks</a>.</p>
<p>With the addition of <code class="language-plaintext highlighter-rouge">search</code>, now every ARIA landmark has a native HTML equivalent. And also, now everyone who has ever written about ARIA landmarks needs to go update their articles. Have fun! <small>(I’m going to update mine later. Don’t @ or DM me.)</small></p>
<h2 id="how-to-use-it">How to use it</h2>
<p>First and foremost, one would use the <code class="language-plaintext highlighter-rouge">search</code> element to expose the search landmark in the browser’s accessibility API, allowing people using assistive technology, such as screen readers, to discover this content area and allow for quick access to it. Being a “search” landmark, it implicitly indicates that the content one would find within would be related to searching for, or even filtering content (filtering is a ‘searching’ behavior… designers sure do like to use the magnify glass icon interchangeably between these UI controls, at least).</p>
<p>Very on the nose, but that’s the point.</p>
<p>Typically a search landmark will contain, at minimum, a text field (either identified as <code class="language-plaintext highlighter-rouge">input type=text</code> or <code class="language-plaintext highlighter-rouge">type=search</code> - <a href="https://adrianroselli.com/2019/07/ignore-typesearch.html">read this if you’re wondering why one wouldn’t necessarily default to just <code class="language-plaintext highlighter-rouge">type=search</code></a>). Additionally, but not necessarily, a button to initiate the search would be another expected descendant of the landmark. Finally, other form controls, descriptive content or links related to understanding or modifying one’s search or filtering query could be present. E.g., checkboxes or radio buttons to modify the parameters.</p>
<p>Markup for a simple search where JavaScript is used to initiate the search functionality:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><search></span>
<span class="nt"><label</span> <span class="na">for=</span><span class="s">s</span><span class="nt">></span>Site<span class="nt"></label></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">search</span> <span class="na">id=</span><span class="s">s</span><span class="nt">></span>
<span class="nt"><button></span>Go!<span class="nt"></button></span>
<span class="nt"></search></span>
</code></pre></div></div>
<p>Markup for a simple search where a traditional form submission is used for the search functionality:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><search></span>
<span class="nt"><form</span> <span class="na">action=</span><span class="s">"search.php"</span><span class="nt">></span>
<span class="nt"><label</span> <span class="na">for=</span><span class="s">"query"</span><span class="nt">></span>Find an article<span class="nt"></label></span>
<span class="nt"><input</span> <span class="na">id=</span><span class="s">"query"</span> <span class="na">name=</span><span class="s">"q"</span> <span class="na">type=</span><span class="s">"search"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">></span>Go!<span class="nt"></button></span>
<span class="nt"></form></span>
<span class="nt"></search></span>
</code></pre></div></div>
<p>Some <a href="https://html.spec.whatwg.org/multipage/grouping-content.html#the-search-element">additional reduced markup <code class="language-plaintext highlighter-rouge">search</code> patterns</a> can be found in the examples of the HTML spec element definition.</p>
<h3 id="why-would-i-use-this-instead-of-form-rolesearch">Why would I use this instead of <code class="language-plaintext highlighter-rouge">form role=search</code></h3>
<p>You might be looking at that second example, where a <code class="language-plaintext highlighter-rouge">form</code> element is nested within the <code class="language-plaintext highlighter-rouge">search</code> element and think “huh… a <code class="language-plaintext highlighter-rouge">search</code> element would still need a <code class="language-plaintext highlighter-rouge">form</code>? That seems like extra work to use two elements.”</p>
<p>And sure, that’s a valid takeaway. Two elements <em>is more than one</em> and you can identify a <code class="language-plaintext highlighter-rouge">form</code> element as a search landmark now by adding a <code class="language-plaintext highlighter-rouge">role=search</code> to it. But when has extra work ever stopped a web developer?</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">role=</span><span class="s">button</span> <span class="na">tabindex=</span><span class="s">0</span><span class="nt">></span>
Sure i could use the button element,
but F that noise.
<span class="nt"></div></span>
<span class="c"><!-- F stands for "forget", obviously... --></span>
<span class="nt"><script></span>
<span class="cm">/*
all the necessary keyboard interactions and behaviors
that now must be recreated to make an accessible "button"
*/</span>
<span class="nt"></script></span>
<span class="nt"><style></span>
<span class="c">/*
all the styles for the default button state, disabled,
hovered, focused, high contrast mode styles,
anything else i'm currently forgetting...
have fun recreating the wheel, yo :)
*/</span>
<span class="nt"></style></span>
</code></pre></div></div>
<p>But seriously, there was quite a bit of back and forth on whether the <code class="language-plaintext highlighter-rouge">search</code> element should be another type of <code class="language-plaintext highlighter-rouge">form</code>, but there was no appetite to do this from the browser implementers (for very reasonable reasons, mind you), and honestly, it is not that big of a deal.</p>
<p>The goal of the element proposal was to provide landmark parity with the available ARIA roles. The element achieves this, and everyone is still perfectly entitled to use <code class="language-plaintext highlighter-rouge"><form role=search></code> for where that would be appropriate. But now, you also <em>don’t have to use ARIA</em>, which is also one of the driving principles of ARIA. As the ARIA spec mentions a few times (the following two quotes taken from different parts of the spec):</p>
<blockquote>
<p>When the host language provides a feature that provides equivalent accessibility to the WAI-ARIA feature, use the host language feature.<br />
…<br />
When a feature in the host language with identical role semantics and values is available, and the author has no compelling reason to avoid using the host language feature, authors SHOULD use the host language features rather than repurpose other elements with WAI-ARIA.</p>
</blockquote>
<p>So, for all you JavaScript developers out there who need to implement a search feature and should be exposing it as a landmark but have no practical use for native <code class="language-plaintext highlighter-rouge">form</code> behaviors, this one’s for you.</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><search>...</search>
vs
<div role=search>...</div>
</code></pre></div></div>
<p>17 vs 23 characters. You’re welcome.</p>
<h2 id="a-final-thought-and-thank-you">A final thought and thank you</h2>
<p>To be brutally honest, this is not the most important element that’s ever been added to the HTML specification. It <em>is</em> however a nice little accessibility win. People don’t <em>have to</em> use ARIA to get this feature. It fills a little hole that has always been present, and every little bit helps.</p>
<p>Additionally, it was a very good experience getting to work on this with the HTML editors (primarily <a href="https://twitter.com/domenic">@domenic
</a>, who helped me with getting the PR done) and fellow ARIA working group member, <a href="https://twitter.com/carmacleod">Carolyn MacLeod</a> who originally made the proposal to HTML to add this element. Further discussing this new element with browser implementers, and identifying a need for some HTML parsing updates (which will be important with other new features coming to HTML) was an interesting, related bit to this process. Maybe more on that another time.</p>
<p>Also, if you’re interested in getting involved with working on the ARIA specification, here’s a nice and easy entry issue to reference the new <code class="language-plaintext highlighter-rouge">search</code> element to get you going: <a href="https://github.com/w3c/aria/issues/1898">ARIA issue #1898 - Add base concept for search role</a>.</p>
<p>But finally, Carolyn unfortunately passed away in February of 2022. This was the last thing I was able to work on with her. Again, while not the biggest win for the accessibility community, I know seeing what she started finally get merged into the spec would have made her happy.</p>
<p>So, I’m really glad this element is now part of HTML, and will soon be available for people to use to markup their web pages. Thanks Carolyn for helping to make this happen.</p>Scott O'HaraToday, March 24th 2023, the HTML specification added a new grouping content element. The search element.Visually hidden content is a hack that needs to be resolved, not enshrined2023-03-21T00:00:00+00:002023-03-21T00:00:00+00:00https://scottohara.me/blog/2023/03/21/visually-hidden-hack<style>
figure {
padding: .5em;
border: 1px solid #444;
font-size: .825em;
background: #fafafa
}
@media screen and (prefers-color-scheme: dark) {
figure { background: #2a2a2a; }
}
</style>
<p>This will retread and extend on my previous <a href="https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html">Inclusively Hidden</a> post. Specifically the parts about “visually hidden” content.</p>
<p>But in lieu of reading that, the reasons one would visually hide content in the development of a website or web application is generally to include extra content for accessibility. For instance, to mitigate against specific design choices, where visually something may be apparent, but programmatically not so much. Or, portions of UI are only need to be visible when needed, but otherwise they need to be hidden <em>but</em> remain accessible so that those using keyboard or assistive technology can find them.</p>
<p>The way to visually hide content, but keep it programmatically available, that is probably the most familiar to many developers is with the following CSS ruleset, or a variation of it:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c">/* sometimes referred to as .sr-only */</span>
<span class="nc">.visually-hidden</span><span class="nd">:not</span><span class="o">(</span><span class="nd">:focus</span><span class="o">)</span><span class="nd">:not</span><span class="o">(</span><span class="nd">:active</span><span class="o">)</span><span class="nd">:not</span><span class="o">(</span><span class="nd">:focus-within</span><span class="o">)</span> <span class="p">{</span>
<span class="nl">clip-path</span><span class="p">:</span> <span class="nb">inset</span><span class="p">(</span><span class="m">50%</span><span class="p">);</span>
<span class="nl">height</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span>
<span class="nl">overflow</span><span class="p">:</span> <span class="nb">hidden</span><span class="p">;</span>
<span class="nl">position</span><span class="p">:</span> <span class="nb">absolute</span><span class="p">;</span>
<span class="nl">white-space</span><span class="p">:</span> <span class="nb">nowrap</span><span class="p">;</span>
<span class="nl">width</span><span class="p">:</span> <span class="m">1px</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>For a breakdown of what each CSS property is doing, check out this <a href="https://www.tpgi.com/the-anatomy-of-visually-hidden/">dissection of the ruleset, by TPGi</a>. <small>Tbh, depending on your browser support matrix, I’ve found that there a few property declarations that can be removed with no negative impact for modern browsers/screen readers. Just something to think about. Maybe <a href="https://codepen.io/scottohara/pen/QWVOqNY">test this out n’ see if you can find something broken</a>? Not endorsing this method, but also, presently unaware of issues - so long as you’re using this to visually hide static content. As in, this ruleset <strong>not intended</strong> to hide interactive controls. But just static content <em>within</em> an interactive control.</small></p>
<p>At the end of that TPGi article, the idea of consolidating this ruleset into a single CSS declaration, for example <code class="language-plaintext highlighter-rouge">display: visually-hidden</code>, was mentioned. This idea was also a brought up in <a href="https://benmyers.dev/blog/native-visually-hidden/">“The Web Needs a Native .visually-hidden”, by Ben Myers</a>. But this is not a new topic, and has been brought up over the years. For instance, <a href="https://github.com/whatwg/html/issues/4623">as a request for an HTML feature</a>, and as different CSS WG issues from 2016.</p>
<p>Full disclosure, I have frequently used and recommended the use of visually hidden content. It’s an important tool that we presently need to help mitigate accessibility gaps for content (design) and features of and related to common web UI. However, while a frequent recommendation, it’s one I would rather I never had to mention again. But, before we get into that, and why I find the idea of the technique becoming any sort of web ‘standard’ rather icky, let’s review some situations where this ruleset is commonly used.</p>
<h2 id="examples-of-visually-hiding-content">Examples of visually hiding content</h2>
<p>Often, one will need a <a href="https://www.w3.org/WAI/WCAG21/Techniques/general/G1">skip to main content link</a> on their website or web application in order to adhere to <a href="https://www.w3.org/WAI/WCAG21/Understanding/bypass-blocks.html">WCAG’s 2.4.1 Bypass Block</a>. The most common way of implementing this instance of a skip link is to visually hide it until it receives keyboard focus. Because a skip link is a navigational mechanism for a sighted keyboard user, after all. <a href="https://www.w3.org/WAI/WCAG21/Understanding/focus-visible.html">So one needs to be able to see what they’ve focused</a> to know whether or not to try and interact with it.</p>
<figure aria-label="Fig1: npr skip link">
<img src="https://scottohara.me/assets/img/articles/skipnpr.png" alt="NPR.org has a skiplink, displayed atop the global banner of their website" />
<p>Not all news websites have a skip to main content link, but <a href="https://www.npr.org">NPR.org</a> does. They have a couple which become visible when they receives keyboard focus.</p>
<p>NPR uses a variation of the visually hidden class.</p>
</figure>
<p>Or, one might visually hide pesky form controls so that they are still available to screen reader and keyboard users. But maybe you wanted to replace them with a <a href="https://scottaohara.github.io/a11y_styled_form_controls/src/radio-button--rating/">fancy ‘custom’ control(s)</a>, allowing for more styling options.</p>
<figure aria-label="Fig2: star rating radios">
<img src="https://scottohara.me/assets/img/articles/starradio.png" alt="radio buttons styled to look like stars. 'rate my rating system' with cancel icon for no selection, and 5 stars with a popup that appears on focus to display the accessible name - e.g., 2 stars" />
<p>Visually styled to look like stars and a cancel icon, these are all native HTML radio buttons and would be exposed as such if styles were turned off or blocked.</p>
<p>Fun fact, these radio buttons don't use the 'typical' visually hidden class.</p>
</figure>
<p>Alternatively, maybe you don’t need to restyle a control, but only want it available on hover and focus. That’s a thing we do to make it more difficult for sighted touch, switch device and speech recognition users to find initially hidden controls. I mean, to visually declutter UI.</p>
<p>But snark aside, there is a constant push and pull between overloading UI with too many controls or too much content. If the UI has too many competing visual features at one time, it can be overwhelming for some users. However, hiding important UI can result in higher difficulty for other users to interact with the page. The struggle is real.</p>
<figure aria-label="Fig3: star rating radios">
<img src="https://scottohara.me/assets/img/articles/plusbutton.png" alt="a line of code in a GitHub pull request showing a 'plus' button that appears on hover/focus to add a comment" />
<p>Each line of code can be commented on with a GitHub pull request. It would be overwhelmingly redundant to visually have a comment button on each line, so it is visually hidden until the line is hovered, or the button receives keyboard focus.</p>
<p>These buttons also don't use the typical visually hidden class.</p>
</figure>
<p>Or or, content might need to be visually hidden because someone wrote some rubbish call-to-action copy and the web page would otherwise be riddled with <a href="https://www.nngroup.com/articles/learn-more-links/">“learn more” or “read more” links</a> that have <a href="https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html">no immediate programmatic context to their purpose</a>.</p>
<p>Maybe a web app was designed in a way where one can infer the current page’s purpose due to the styling of the “current page link” in the primary nav - so there’s no visible heading to introduce the primary content of the page. Oh, and adding that heading in there would “degrade the design”, so cool. Guess we can insert a visually hidden heading to help screen reader users, at least, without disrupting the design which would be absolutely ruined and unusable if a heading was added. <a href="https://webaim.org/projects/screenreadersurvey9/#heading">Horrible, horrible headings…</a></p>
<figure aria-label="Fig4: visually hidden h1">
<img src="https://scottohara.me/assets/img/articles/homeh1.png" alt="facebook's h1 is visually hidden for their home screen" />
<p>Rather than display the primary heading of the page, Facebook visually hides it.</p>
<p>The visually hidden technique appears to be used here, but has been obfuscated into various single-purpose compiled classes. <code>.x1heor9g</code> is a real banger of a utility class, if I do say so myself.</p>
</figure>
<p>Or or or, a website or web application requires a ‘global’ live region component. For instance, rather than having multiple live regions on a web page, a single live region is used to expose dynamic updates to a page. <a href="https://www.scottohara.me/blog/2022/02/05/are-we-live.html">Live regions can be quite annoying to get right</a>, so a persistent visually hidden live region is often used for complex web apps, so as to work around having to have tons of empty live regions in a screen, waiting for content to be added to them so they can inform screen reader users that “You must fill out your name. It is REQUIRED!!!! SURE I KNOW YOU’RE JUST TABBING THROUGH THE PAGE RIGHT NOW TO CHECK OUT WHAT THIS FORM INCLUDES, BUT DAMNIT I NEED YOUR NAME! I NEED IT NOW!!!! NOW!!!!!” <small>I may have some thoughts about how aggressive web forms are with on-blur error messaging</small>.</p>
<figure aria-label="Fig5: screen reader only live region">
<img src="https://scottohara.me/assets/img/articles/sronly.png" alt="visually hidden live region appearing at the bottom of the DOM" />
<p>The most on the nose instance of the <code>.sr-only</code> class, being used to visually hide a global ARIA live region as the last element in a web page.</p>
</figure>
<p><span title="or-n't you glad I didn't say 'or' again?">There are other instances</span> of where you may want content to be hidden but available only for people using assistive technology, or only when the hidden content is actually relevant (e.g., a sub-navigation list of links). These would be different from the previously mentioned examples, as these would not be situations where the content should be treated as visually hidden. Rather, they would generally need to be hidden to everyone. Again, see my <a href="https://www.scottohara.me/blog/2017/04/14/inclusively-hidden.html">previous blog post</a> for more information on stuff like that.</p>
<h2 id="sure-does-seem-we-need-to-visually-hide-stuff-quite-a-bit">Sure does seem we need to visually hide stuff quite a bit?</h2>
<p>If there’s a need for so many instances of visually hidden content, why wouldn’t it be standardized? Well, I mean standardized beyond the already existing ways to visually hide content:</p>
<ul>
<li><code class="language-plaintext highlighter-rouge">color: transparent</code></li>
<li><code class="language-plaintext highlighter-rouge">opacity: 0</code></li>
<li><code class="language-plaintext highlighter-rouge">transform: scale(0)</code></li>
<li><code class="language-plaintext highlighter-rouge">clip-path: inset(50%)</code></li>
<li>etc.</li>
</ul>
<p>By themselves, each of these properties be used to visually hide content per the situation, but someone might have to add a few extra property declarations, e.g., <code class="language-plaintext highlighter-rouge">position: absolute</code>, to get the content to no longer take up visual space in the document layout. Or, a combination of some of the above or other properties may be necessary to fit one’s exact visually-hidden needs. For instance, if creating a custom checkbox style, one might think to just use the standard visually hidden class.</p>
<p id="checkbox-hack">
However, as <a href="https://scottaohara.github.io/a11y_styled_form_controls/src/checkbox/">I've been noting for some time now</a> and others have since mentioned, using the standard visually hidden class will introduce problems due to the 1px by 1px nature of the beast/ruleset.
</p>
<p>Remember, not all screen reader users are completely blind. Some of these users, for instance, might be able to see or partially see a custom checkbox on their iOS or Android device. They might then think to press or drag their finger across their screen to <a href="https://support.apple.com/guide/iphone/learn-voiceover-gestures-iph3e2e2281/ios">“explore by touch”</a> while the screen reader is enabled, only to then be seemingly unable find this checkbox that visually appears to be there, but is really shoved off into a 1px by 1px square somewhere on the page. Fun!</p>
<p>
Heck, this problem would exist even for instances of not completely restyling a checkbox from the ground up (which also, unless you're going way out there with a custom style, <a href="https://www.scottohara.me/blog/2021/09/24/custom-radio-checkbox-again.html">we don't really need to do anymore</a>.
</p>
<p>Consider a button (such as a “more options”, or “edit”, “delete”, etc.) that is visually hidden in a ‘card’ or ‘row’ of content, where it only becomes visible on hover (generally of the whole container), or focus/focus-within the container. If the standard visually hidden class was used here, this too would suffer from the 1px by 1px problem. (You may remember I mentioned GitHub has a visually hidden comment button for reviewing PRs, but it doesn’t use the standard visually hidden technique. They don’t have this 1px by 1px problem!)</p>
<p>A standardization of the ruleset wouldn’t help with these situations. Actually, it’d probably make the problem more prevalent, since many developers would (understandably) think all they needed to do was to set this fabled single property, and all their accessibility problems concerning content that needed to be hidden would go away. Just like ARIA, it’s there to make your content <em>more accessible</em>.</p>
<p>Wait… what? ARIA gets used in situations where it is either unnecessary or not appropriate for the use case? Shocking.</p>
<h3 id="what-about-fixing-the-underlying-problems-instead">What about fixing the underlying problems instead?</h3>
<p>Getting caught up in how to make visually hiding content easier for developers to implement (you know, so they don’t have to copy/paste a class name), imo, is missing the mark on solving the real issue. We “need” to visually hide content because of gaps in the design of the UI, or because of features which are lacking from browsers.</p>
<p>For many of the examples called out earlier, what if those gaps were solved instead?</p>
<h4 id="skip-to-content-links-are-also-a-dirty-hack">Skip to content links are also a dirty hack</h4>
<p>We shouldn’t need skip to content links. Rather, what would be way more useful that including a visually hidden link at the top of every website, is <a href="https://github.com/whatwg/html/issues/5813">if browsers would support navigation by elements that are exposed as landmark roles</a>. At the very least, allow for a mechanism to directly move focus to the content of the main element/landmark. Alternatively, allowing for quick navigation by headings would be pretty awesome, too.</p>
<p>Implementing one of those ideas could negate the need for skip to main content links, at least. You don’t need to visually hide something that isn’t necessary to implement, after all.</p>
<h4 id="styling-form-controls-isnt-as-bad-as-it-used-to-be">Styling form controls isn’t as bad as it used to be</h4>
<p>Yes, visually hiding a form control so that a custom styled ‘faux’ control can be rendered in its place is rather common. <a href="#checkbox-hack">But as mentioned</a>, the standard visually hidden ruleset is not what you should be using to effectively hide the few controls that even allow for this hack. Also, wouldn’t it be far better if time was instead spent trying to push <a href="https://open-ui.org/components/checkbox/">proposals like a new checkbox element</a> over the line?</p>
<p>IMO, new checkbox and radio elements are not the most important features we need on the web. But if given the option between those and a standardized visually hidden feature, yes. Please. New checkboxes and radios. Solve for the problem, not the symptom.</p>
<h4 id="regarding-aria-label-and-insufficient-ctas">Regarding <code class="language-plaintext highlighter-rouge">aria-label</code> and insufficient CTAs</h4>
<p>A web page containing a bunch of buttons or links with, for example, “edit” or “learn more” visible text, and thus accessible names, can be rather rubbish depending on the way one is navigating the web page.</p>
<p>For instance, a page listing article or product previews, each with with a “learn more” call to action link. Without extra development effort, all these links will simply be exposed as “learn more” when someone navigates by focusable elements, or uses their screen reader to navigable specifically by links.</p>
<figure aria-label="Fig6: learn more rotor">
<img src="https://scottohara.me/assets/img/articles/learnmore.png" alt="VoiceOver rotor showing a skip to main, back to top, and several 'learn more' links" />
<figcaption><p>Before you follow these links to learn more, you're going to need to learn more about what you'd be learning more about.</p></figcaption>
</figure>
<p>Code for what could produce the above link listing might look something like this:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"card-or-whatever"</span><span class="nt">></span>
<span class="nt"><h3></span>Here's some descriptive text but lol, I'm not a link!<span class="nt"></h3></span>
<span class="nt"><p></span>Fluffy fluff fluff tells you stuff about the fluff.<span class="nt"></p></span>
<span class="nt"><p><a</span> <span class="na">href=</span><span class="s">...</span><span class="nt">></span>Learn more!<span class="nt"></a></p></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>In these contexts where the link serves as a standalone CTA outro to UI elements like ‘cards’, these can be flagged as <a href="https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-in-context.html">2.4.4 Link Purpose (In Context)</a> failures. This is because the CTA is not programmatically associated with other text (e.g., a paragraph) to help provide the context as to what one would be learning more about.</p>
<p>This is why such CTAs are commonly recommended to use the visually-hidden technique to provide more context. For instance,</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">...</span><span class="nt">></span>
Learn more
<span class="nt"><span</span> <span class="na">class=</span><span class="s">visually-hidden</span><span class="nt">></span>about whatever yada yada<span class="nt"></span></span>
<span class="nt"></a></span>
</code></pre></div></div>
<p>However, while often used to alleviate this lack of context, there are other avenues we could be considering, instead.</p>
<p>For example, <code class="language-plaintext highlighter-rouge">aria-label</code> and <code class="language-plaintext highlighter-rouge">aria-labelledby</code> can allow for the same context to be provided, without the need for the visually hidden hack.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"..."</span> <span class="na">aria-label=</span><span class="s">"Learn more about something"</span><span class="nt">></span>
Learn more
<span class="nt"></a></span>
...
<span class="nt"><p</span> <span class="na">id=</span><span class="s">thing</span><span class="nt">></span>My address, or whatever<span class="nt"></p></span>
...
<span class="nt"><button</span> <span class="na">id=</span><span class="s">self</span> <span class="na">aria-labelledby=</span><span class="s">"self thing"</span><span class="nt">></span>
Edit
<span class="nt"></button></span>
</code></pre></div></div>
<p>Granted, right now <a href="https://adrianroselli.com/2019/11/aria-label-does-not-translate.html#Update04">translation services and <code class="language-plaintext highlighter-rouge">aria-label</code> continue to have issues</a>, and using <code class="language-plaintext highlighter-rouge">aria-labelldby</code> has the problem that it necessitates the use of IDs, and developers hate IDs.</p>
<p>So, maybe ARIA isn’t an option for you. But you know what that leaves? Making a better design.</p>
<p>For instance, I’ve already referenced the Nielsen Group article <a href="https://www.nngroup.com/articles/learn-more-links/">“Learn More” Links: You Can Do Better</a>, but I will do so again because it is worth repeating.</p>
<p>Take a look at many popular news aggregate websites <small>(well, look at them just in the context of this train of thought… I can’t endorse them otherwise, unless you’re looking to significantly up your anxiety and tank your mood. News sites do that great.)</small>. Many of which I’ve reviewed show they’ve since moved away from the “learn/read more” CTA approach and instead treat the concise but descriptive titles/headings of their article previews as the links to their content.</p>
<p>“But Scott, user testing has shown that CTAs get more clicks”</p>
<p>Well sure, I didn’t think we needed a test to determine if someone sees a big button-looking control that screams “click me”, they will click it. But visual cues to visually promote “click me” behaviors don’t “need” to impact the accessibility of the content.</p>
<p>For instance, there’s no reason one couldn’t implement a CTA as more of a decorative element that still allowed for pointer events to activate it. A very quick example (and one of the demos even still has the lovely ‘read more’ appear on hover/focus):</p>
<p class="codepen" data-height="522" data-default-tab="html,result" data-slug-hash="LYJgbrd" data-user="scottohara" style="height: 522px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/LYJgbrd">
simple cta article 'card'</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<p>I’m sure many could come up with more creative designs than that five minute codepen. But maybe take this as a challenge. Accessible design doesn’t need to be boring, rather good accessible design demands creativity. So, be creative and move away from the uninspiring learn more links and visually hidden hack.</p>
<h4 id="we-need-a-notification-api">We need a notification API</h4>
<p><a href="https://www.scottohara.me/blog/2022/02/05/are-we-live.html">Live regions are finicky</a>, and having to implement a visually hidden live region to act as a notification center for a website/web application is yet another hack built atop a shouty house of cards.</p>
<p>Rather, if we had <a href="https://wicg.github.io/aom/notification-api.html">a notification API</a>, we wouldn’t need to visually hide live regions at all. Rather, we would be able to dynamically render the content we want on our web pages, and then use the API to ensure the rendered text content, or maybe even something more concise/informative than what is visually rendered, could be exposed to the accessibility tree. No reliance on DOM elements, no problems where the live region that should have handled this is within an <code class="language-plaintext highlighter-rouge">inert</code> or <code class="language-plaintext highlighter-rouge">aria-hidden=true</code> subtree because a modal dialog opened and blew your aforementioned shouty house of cards down.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><dialog></span>
Imma be a modal dialog!
<span class="nt"><button></span>
Press me and make a toast show up,
but LOL don't expect to hear it!
<span class="nt"></button></span>
<span class="nt"></dialog></span>
...
<span class="nt"><div</span> <span class="na">class=</span><span class="s">visually-hidden</span> <span class="na">aria-live=</span><span class="s">polite</span><span class="nt">></span>
Inject whatever you want into me, and I'll typically
announce it. But, I'm not saying
"boo" if that modal dialog is open!
<span class="nt"></div></span>
</code></pre></div></div>
<p>You want to get around this? Either don’t use the native dialog element (which would be quite a shame, as using it responsibly can be far better than creating your own). Or, put <strong>another</strong> live region into your dialog, so your page can continue to shout things while the modal dialog is open and everything outside of it is treated as inert.</p>
<h2 id="wrapping-up">Wrapping up</h2>
<p>For better and worse, we will have instances where we need to visually hide content to mitigage a gap in the visual design. That’s not to say that every gap is necessarily due to a failure to incorporate accessibility in the design process. Rather, because there is a balance that needs to be maintained between what is visually presented, vs what is programmatically conveyed, sometimes the best way to work around that is to inject content that is specifically for people using assistive technology.</p>
<p>Them’s the breaks.</p>
<p>But, as I’ve hopefully illustrated by now, while there is a common ruleset for visually hiding content, codifying that to me seems more of a silent acknowledgement that a hack is the best we can do. I don’t agree. The web should be better than that.</p>
<ul>
<li>We <em>should</em> be able to navigate to landmarks (e.g., skip to main content) by default browser commands.</li>
<li>We <em>should</em> be able to send dynamic messages to assistive technology, without having to rely on fragile, invisible, live regions.</li>
<li>We <em>should</em> be able to use <code class="language-plaintext highlighter-rouge">aria-label</code> (and also <code class="language-plaintext highlighter-rouge">aria-description</code>) without having to worry about translation services.</li>
<li>We <em>should</em> be able to fully and easily style common form controls.</li>
<li>And we <em>shouldn’t</em> need to introduce a native way to do all these things, which covers various use cases for visually hidden content, but not all of them.</li>
</ul>
<p>Introducing a native mechanism to save developers the trouble of having to use a wildly available CSS ruleset doesn’t solve any of those underlying issues. It just further pushes them under the rug. Additionally, it introduces an “official” way to hide content which at best, might not be the right way to hide content for a particular use case. And at worse, it provides a hiding mechanism for people to misuse. Content that <em>should</em> be hidden to everyone could get the “well, isn’t it <em>more accessible</em> if we never truly hide content, so that screen reader users can always have access to everything at all times?”</p>
<p>No. That’d be quite problematic. And while paraphrased, that <em>is</em> a question I’ve been asked, when having to mention to someone that they’ve incorrectly hidden content. While this thinking comes from a good place, it is exactly what we <em>don’t</em> want to happen.</p>
<p>Now, one might say that education and examples are the ways to promote correct usage. And that person would be right. But also, if documentation and providing official examples would mitigate misuse and misunderstandings, then we wouldn’t need stack overflow or slack channels for people to post questions, daily, or online courses to explain to developers what existing free resources already cover.</p>
<p>Anyway, that’s it. Those are my very not-hidden thoughts on the topic. Thanks for reading all these yucky words. So so many words…</p>Scott O'HaraHaving an AI dialog2023-02-17T00:00:00+00:002023-02-17T00:00:00+00:00https://scottohara.me/blog/2023/02/17/an-ai-dialog<p>I’ve already talked about <a href="https://www.scottohara.me/blog/2023/01/31/ai-a11y-maybe-no.html">Setting expectations for asking ChatGPT web accessibility questions</a>. So you should probably go read that first.</p>
<p>But as a quick a reminder, ChatGPT’s responses are derived from interpreting the question it was asked, and crafting its response based on what it has ‘learned’ <em>from us</em>, via what we’ve published to the Internet. In regards to how accurate it’s responses are to accessibility related questions, they may be rather high level and “accurate”, but can also quickly veer into laughably (depressingly) incorrect territory. The accuracy of what you get back will also be contingent on how you ask your question. For example, if it’s too broad then you’ll likely get a broad response back. If it’s a very focused, but potentially leading, question then you could very well get a response back that is biased to your ask.</p>
<p>In my previous post, I had asked a very high level question on how to make an accessible button for my web page. For something that should be so simple, there was a lot of inaccurate (i.e., wrong or u) information returned.</p>
<p>So what do we get back if you ask about something more complex? Something that has a long history of developer pain points and accessibility quirks and gaps with custom implementations? Something that, for all its past troubles, has finally reached a point of being rather straight forward to implement <strong>and</strong> the browser takes care of the majority of its accessibility requirements for you?</p>
<h2 id="having-an-ai-dialog">Having an AI dialog</h2>
<p>If this is your first time coming to my blog, hi. I’m Scott. I’ve spent too much time working with and talking about the accessibility of web dialogs. However, today I don’t have anything specifically new about dialogs that I want to go over. Go read this if you want <a href="https://www.scottohara.me/blog/2023/01/26/use-the-dialog-element.html">my latest take on the <code class="language-plaintext highlighter-rouge">dialog</code> element</a>. Instead, I’m very curious to see what ChatGPT has to say about something that has such a long history of being inadequately implemented, in regards to accessibility, by web developers.</p>
<p>The purpose of this will be to demonstrate what ChatGPT gets right (because to be fair, it does get some stuff right), vs what it gets wrong. And boy howdy to that…</p>
<h2 id="what-is-a-web-dialog">What is a web dialog?</h2>
<p>So I asked ChatGPT what a <em>web</em> dialog is. Seems we should setup some level of a baseline.</p>
<p>I included the word ‘web’ because otherwise it would have given a high level response about what a conversation between two people is, as well as a brief mention of a dialog also being a GUI element for web and desktop applications.</p>
<blockquote>
<p>A web dialog, also known as a modal or a lightbox, is a user interface component that appears on top of the current webpage, typically as a pop-up window or box. These dialogs are designed to provide additional information, options or controls for the user, and can include text, images, forms, and other interactive elements. Web dialogs are often used to display login forms, subscription forms, confirmations, alerts, and other types of interactions, that are not part of the main content of the webpage. Examples of web dialogs include a pop-up window that appears when you try to leave a webpage, a subscription form that appears as a pop-up after clicking on a sign-up button.</p>
</blockquote>
<p>So this is both an example of what ChatGPT can do well, as well as an example of “neat, I got a search result without any author attribution by typing into a chat interface”. Nothing inaccurate here. Also nothing anyone specifically needed ChatGPT for.</p>
<p>So let’s move onto something more specific to web accessibility.</p>
<h3 id="question-how-can-i-create-an-accessible-web-dialog">QUESTION: how can I create an accessible web dialog?</h3>
<p>The following are <strong>two different responses</strong> that ChatGPT gave me to the same question during two different sessions.</p>
<h4 id="first-answer">First Answer</h4>
<blockquote>
<p>There are a few best practices you should follow when creating an accessible web dialog:</p>
<ol>
<li>Use clear and descriptive labels: Make sure that all buttons and controls within the dialog are labeled clearly and accurately. This will help users understand what they are interacting with and what actions they can take.</li>
<li>Provide clear instructions: Provide clear instructions on how to interact with the dialog, such as how to close it or move between different options.</li>
<li>Use ARIA roles: ARIA (Accessible Rich Internet Applications) roles can be used to provide additional information about the dialog to assistive technology, such as screen readers. You can use roles such as “dialog” and “alertdialog” to indicate that the content is a dialog and to let users know that they need to take action.</li>
<li>Create a contrast of colors: Make sure that the contrast between the text and background colors is high enough for users with low vision to read the content.</li>
<li>Use a “modal” dialog: A modal dialog is a dialog that requires the user’s attention before they can continue to interact with the rest of the website. It prevents users from interacting with the rest of the website while the dialog is open.</li>
<li>Make the dialog keyboard-navigable: When creating a dialog, make sure that users can also interact with it using just their keyboard. This way, users who use assistive technology, such as screen readers, can still access the dialog and interact with its controls.</li>
<li>Test for accessibility: After creating the dialog, test it using assistive technology to make sure that it is accessible for all users.
By following these guidelines, you can create an accessible web dialog that can be used by all users, regardless of their abilities.</li>
</ol>
</blockquote>
<h5 id="breakdown-of-first-answer">Breakdown of First Answer</h5>
<ol>
<li>The initial item in the returned list of best practices is spot on. It’s high level, but my original ask was rather high level as well.</li>
<li>The second point seems OK at first glance, but then when it tries to clarify about what sort of clear instructions are needed, it immediately gets into some odd advice. Can you think of a web dialog you’ve interacted with where there was text provided informing you that to close the dialog you can press the “close” button, or use the <kbd>Esc</kbd> key? Or how to move to the different interactive elements within the dialog? Hopefully most people would know to interpret this as it should have been stated “provide clear and consicse instructions on how to interact with the content of the dialog, as necessary”. One does not need instructions on how to perform basic tasks, unless this is some nightmare dialog from hell where up is down, left is red and close buttons within it shoot laser beams and hurl crackers at napping cats. If your dialog does those things, go ahead and provide instructions. Please.</li>
<li>Regarding “use ARIA roles… ‘dialog’ and ‘alertdialog’…” nothing wrong with that on its surface, except for the fact that it doesn’t indicate when to use which role (probably a more clarifying question one could ask) - but it also fails to mention that you don’t need to use ARIA at all. Just <a href="https://www.scottohara.me/blog/2023/01/26/use-the-dialog-element.html">use the <code class="language-plaintext highlighter-rouge">dialog</code> element (reasonably)</a>.</li>
<li>I chuckled a little bit at “create a contrast of colors”, but outside of the initial odd wording, it’s not wrong. It’s also advice that goes beyond specifically a dialog. But hey, doesn’t hurt to remind people to always be contrasting (their colors).</li>
<li>Huh… using a modal dialog is only a best practice if you actually want a modal dialog though. Would someone know to ask for clarity on what this means and how it differs from ‘use ARIA roles… ‘dialog’ and ‘alertdialog’? I hope so.</li>
<li>A mixing of information here. Being keyboard-navigable is important to people who have full vision, low vision, or who are blind. That aside, again this is a more global concept that is not unique to dialogs.</li>
<li>As mentioned in my previous post, testing using assistive technology may be a big ask for someone who is unfamiliar with assistive technology, or who might be aware of it but has no idea how to actually use it.</li>
</ol>
<blockquote>
<p>By following these guidelines, you can create an accessible web dialog that can be used by all users, regardless of their abilities.</p>
</blockquote>
<p>But hey, if you follow these guidelines then you can rest assured that whether someone can whistle or not, they can use a dialog. Oh, wait. No, it probably means that a dialog can be used by people with disabilities. Yeah, that’ll all come down to the implementation. But before we get to how ChatGPT says to do that, let’s look at what it <em>also said</em> when asked the same question during another session.</p>
<h4 id="second-answer">Second Answer</h4>
<blockquote>
<p>There are several ways to make a web dialog accessible, which will provide a better user experience for everyone, including users with disabilities. Here are a few important things to keep in mind when designing an accessible web dialog:</p>
<ol>
<li>Provide a descriptive title: The title of the dialog should be meaningful and accurately describe the purpose of the dialog.</li>
<li>Make sure the dialog is keyboard accessible: Make sure that users can operate the dialog using just a keyboard and that there are no keyboard traps.</li>
<li>Close the dialog with the Escape key: This is a commonly understood key for closing dialogs.</li>
<li>Make sure the dialog is screen-reader friendly: Use proper ARIA roles, labels and attributes to make the dialog accessible to screen readers.</li>
<li>Focus management: Ensure that the focus is moved to the dialog when it opens and is restored to the triggering element when the dialog closes.</li>
<li>Provide a clear and visible dismissal button: A close button should be visible and easily located.</li>
<li>Take care of accessibility in form controls: If the dialog contains any form controls, ensure they are labeled and can be filled in by assistive technologies.</li>
<li>Creating an accessible web dialog takes a bit of extra effort, but it is worth it in the end. By doing so, you can make your website more user-friendly and accessible to people with disabilities.</li>
</ol>
</blockquote>
<p>I’m not going to break this response down, but I will acknowledge that outside of a few nits it is a far better response than the first. Kinda like it’s a different person on call for the help desk. I can buy that, not everyone who is an accessibility practitioner knows everything about accessibility. Some people know more about accessible UX concepts, some others about legal compliance, and while others may know about technical implementations. Any one of those people might give you a variation on a response, and include their own opinionated or even inaccurate information to boot as they try to smooth over gaps in what they know they should cover.</p>
<p>I mean, people <em>shouldn’t</em> necessary do that - smooth over the gaps. But, when one has to take on the role of being the expert in a room full of people that don’t know a div from a button, there is a lot of pressure to know the answer. I wonder how much better off web accessibility would be if people instead said “huh, I don’t know. Let’s do some testing and find out!” - rather than “screen reader users will be confused and WCAG is going to steal your lunch and step on it if you don’t make that link into a button”.</p>
<p>OK, maybe that’s not an entirely accurate representation of what people say. WCAG is far more partial to breakfast items…</p>
<p>Anyway, high level guidance aside, people have expressed that they <em>want</em> ChatGPT to give them recommendations for how to write accessible components. And some people have indicated that <em>they think</em> ChatGPT can do just that. So…</p>
<h3 id="how-do-i-build-a-c-between-two-people-gui-aria-role-lightbox-thing-modal">How do I build a c between two people GUI ARIA role lightbox thing, modal?</h3>
<p>Asking ChatGPT how to code an ‘accessible dialog’, or ‘accessible web dialog’, will return variations on the same sort of answer each time you ask. The following show two wildly different responses, each with their own issues.</p>
<p><strong>Note:</strong> I have left out the example JavaScript and CSS that was returned for each. Reviewing them wouldn’t yield any more important insights, and this is already getting too long.</p>
<h5 id="first-answer-to-show-me-the-code-for-an-accessible-dialog">First Answer to “show me the code for an accessible dialog”</h5>
<blockquote>
<p>Here is an example of the code for an accessible dialog:</p>
</blockquote>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal"</span> <span class="na">tabindex=</span><span class="s">"-1"</span> <span class="na">role=</span><span class="s">"dialog"</span> <span class="na">aria-labelledby=</span><span class="s">"exampleModalLabel"</span> <span class="na">aria-hidden=</span><span class="s">"true"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-dialog"</span> <span class="na">role=</span><span class="s">"document"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-content"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-header"</span><span class="nt">></span>
<span class="nt"><h5</span> <span class="na">class=</span><span class="s">"modal-title"</span> <span class="na">id=</span><span class="s">"exampleModalLabel"</span><span class="nt">></span>Modal title<span class="nt"></h5></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">class=</span><span class="s">"close"</span> <span class="na">data-dismiss=</span><span class="s">"modal"</span> <span class="na">aria-label=</span><span class="s">"Close"</span><span class="nt">></span>
<span class="nt"><span</span> <span class="na">aria-hidden=</span><span class="s">"true"</span><span class="nt">></span><span class="ni">&times;</span><span class="nt"></span></span>
<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-body"</span><span class="nt">></span>
...
<span class="nt"></div></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"modal-footer"</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">class=</span><span class="s">"btn btn-secondary"</span> <span class="na">data-dismiss=</span><span class="s">"modal"</span><span class="nt">></span>Close<span class="nt"></button></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"button"</span> <span class="na">class=</span><span class="s">"btn btn-primary"</span><span class="nt">></span>Save changes<span class="nt"></button></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
<span class="nt"></div></span>
</code></pre></div></div>
<blockquote>
<p>Here’s what’s happening in the code:</p>
</blockquote>
<blockquote>
<ul>
<li>The <code class="language-plaintext highlighter-rouge"><div></code> with a class of “modal” acts as the overall container for the dialog. It is given a “tabindex” attribute of “-1” to ensure that it can be programmatically focused.</li>
<li>The “role” attribute is set to “dialog” to indicate that it is a dialog, and “aria-labelledby” is used to associate the heading with the appropriate label.</li>
<li>The “aria-hidden” attribute is set to “true” to indicate that the dialog is not visible by default.</li>
<li>The “modal-dialog” and “modal-content” classed <code class="language-plaintext highlighter-rouge"><div></code>s serve as a container for the content of the dialog, with “role” attribute set to “document”.</li>
<li>The “modal-header” classed <code class="language-plaintext highlighter-rouge"><div></code> contains the heading for the dialog, “Modal Title”, and a button that can be used to close the dialog. The “aria-label” attribute is used to indicate to assistive technology that the button is used to close the dialog.</li>
<li>The “modal-body” classed <code class="language-plaintext highlighter-rouge"><div></code> is where the content of the dialog goes.</li>
<li>The “modal-footer” classed <code class="language-plaintext highlighter-rouge"><div></code> contains buttons that allow the user to interact with the dialog, e.g. close it or save changes.</li>
<li>It’s important to note that this is a basic example and different frameworks or libraries have different ways of creating dialogs. In any case, these are the fundamental guidelines to creating an accessible dialog.
You can also check the framework’s documentation to get more information about accessibility and how to create accessible dialogs.</li>
</ul>
</blockquote>
<hr />
<p>This is probably one of the better answers I’ve gotten from ChatGPT for the coding of an accessible component, but there are some missing bits here. Granted, the response does call out that it’s a basic example, and to check frameworks and libraries for more info… which is all well and good if someone is using a framework or component library. But since I was asking ChatGPT to show me the code for an accessible dialog, it could have served as a signal to the AI that maybe if I was already using a framework or library, I would have checked these resources already? Common sense as a hot fix maybe?</p>
<p>Regardless, while not horrible (we’ll get to that) it’s also missing important information. For instance,</p>
<ul>
<li>Using <code class="language-plaintext highlighter-rouge">aria-hidden=true</code> is a red herring. A dialog when not shown will most often be hidden using CSS <code class="language-plaintext highlighter-rouge">display: none</code>, and if that’s the case then it’s already hidden to all users. <code class="language-plaintext highlighter-rouge">aria-hidden=true</code> is just extra ARIA for no real benefit, and really only introduces the possibility of this creating a bug (e.g., the dialog is visually rendered, but <code class="language-plaintext highlighter-rouge">aria-hidden=true</code> remains on the dialog).</li>
<li>While not a compliance issue, some people might get a bit ruffled about the fact that the <code class="language-plaintext highlighter-rouge"><h5></code> element is used as the initial heading for this dialog. Using this particular heading makes me think that ChatGPT was <a href="https://github.com/twbs/bootstrap/issues/37179">goinking this code example from Bootstrap</a>. Note that there’s no ‘WCAG failure’ here, but generally people would expect a <code class="language-plaintext highlighter-rouge"><h1></code> or <code class="language-plaintext highlighter-rouge"><h2></code> as the initial dialog heading.</li>
<li>There’s no mention here about keeping keyboard focus restricted to the modal dialog when its open. Nor use of <code class="language-plaintext highlighter-rouge">aria-modal</code> in an attempt to restrict a screen reader’s navigation to just the modal dialog. Those are kinda important bits that were left off.</li>
<li>There’s no mention as to why the <code class="language-plaintext highlighter-rouge">role=document</code> is included in the markup (this is generally for legacy screen reader behavior, or even for more modern screen reader bugs… but it <em>shouldn’t</em> be necessary outside of these mitigating reasons).</li>
</ul>
<p>But that’s enough on this for now. Let’s look at the other response I got…</p>
<h5 id="second-answer-to-show-me-the-code-for-an-accessible-web-dialog">Second answer to “show me the code for an accessible web dialog”</h5>
<blockquote>
<p>Sure, here’s an example of HTML, CSS, and JavaScript code that you can use to create an accessible web dialog:</p>
</blockquote>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span> <span class="na">id=</span><span class="s">"open-dialog"</span><span class="nt">></span>Open Dialog<span class="nt"></button></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"dialog"</span> <span class="na">aria-hidden=</span><span class="s">"true"</span> <span class="na">aria-labelledby=</span><span class="s">"dialog-title"</span><span class="nt">></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">"dialog-title"</span><span class="nt">></span>Sign in<span class="nt"></div></span>
<span class="nt"><form</span> <span class="na">id=</span><span class="s">"sign-in-form"</span><span class="nt">></span>
<span class="nt"><label</span> <span class="na">for=</span><span class="s">"email"</span><span class="nt">></span>Email:<span class="nt"></label></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"email"</span> <span class="na">id=</span><span class="s">"email"</span> <span class="na">name=</span><span class="s">"email"</span> <span class="na">required</span><span class="nt">></span>
<span class="nt"><label</span> <span class="na">for=</span><span class="s">"password"</span><span class="nt">></span>Password:<span class="nt"></label></span>
<span class="nt"><input</span> <span class="na">type=</span><span class="s">"password"</span> <span class="na">id=</span><span class="s">"password"</span> <span class="na">name=</span><span class="s">"password"</span> <span class="na">required</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">type=</span><span class="s">"submit"</span><span class="nt">></span>Sign in<span class="nt"></button></span>
<span class="nt"></form></span>
<span class="nt"><button</span> <span class="na">id=</span><span class="s">"close-dialog"</span><span class="nt">></span>Close<span class="nt"></button></span>
<span class="nt"></div></span>
</code></pre></div></div>
<blockquote>
<p>In this example, the dialog will open when a user clicks the “Open Dialog” button and close when either the “Close” button or the “Escape” key is pressed. The close button closes the dialog, and ARIA attributes set to “aria-hidden” are used for screen readers, which will not read elements with aria-hidden=”true”, Also focus management done on the dialog, and the submit event handled.
This is just one possible way to create an accessible web dialog, and it can be modified.</p>
</blockquote>
<hr />
<p>So this response is rather awful. The worst I’ve come across, actually. The JS I excluded did set focus to the email field of the dialog as part of the click event for the open dialog button, and it did toggle <code class="language-plaintext highlighter-rouge">aria-hidden</code> from true to false, while adding/removing <code class="language-plaintext highlighter-rouge">display:none</code> and <code class="language-plaintext highlighter-rouge">block</code> - essentially making the use of <code class="language-plaintext highlighter-rouge">aria-hidden</code> absolutely pointless. But, yeh. There’s nothing about this that would otherwise make it an accessible modal dialog. It has no dialog role, it lacks an <code class="language-plaintext highlighter-rouge">aria-modal=true</code>. <code class="language-plaintext highlighter-rouge">aria-labelledby</code> on a generic <code class="language-plaintext highlighter-rouge">div</code> element is prohibited to web authors, so that’s wrong. And, it says nothing about the fact that the content outside of the dialog needs to be treated as inert.</p>
<blockquote>
<p>This is just one possible way to create an accessible web dialog, and it can be modified.</p>
</blockquote>
<p>No, it is <strong>not</strong>. The only part about this that’s true is that “it can be modified”, and it should. Modified right into the trash.</p>
<h3 id="so-scott-if-youre-so-picky-what-should-the-answer-be">So, Scott, if you’re so picky what “should” the answer be?</h3>
<p>It’d be swell if instead of providing half answers, or incorrect answers, AI could instead be trained to know which resources provide higher quality content. For instance, giving a brief answer and pointing people the the <a href="https://www.w3.org/WAI/ARIA/apg/patterns/dialog-modal/">ARIA Authoring Practices Guide for a modal dialog</a>, or even just pointing to the <a href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-dialog-element">HTML specification’s <code class="language-plaintext highlighter-rouge"><dialog></code> element</a>, or the <a href="https://developer.mozilla.org/en-US/docs/Web/HTML/Element/dialog">MDN document about <code class="language-plaintext highlighter-rouge">dialog</code></a>.</p>
<p>But, those authoratative resources aside, the easiest and most robust code snippet to provide would be:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span> <span class="na">id=</span><span class="s">b</span><span class="nt">></span>
Open the dialog <span class="c"><!-- change this text as appropriate --></span>
<span class="nt"></button></span>
<span class="nt"><dialog</span> <span class="na">aria-labelledby=</span><span class="s">h</span><span class="nt">></span>
<span class="nt"><h2</span> <span class="na">id=</span><span class="s">h</span><span class="nt">></span>my dialog's title<span class="nt"></h2></span>
<span class="nt"><form</span> <span class="na">method=</span><span class="s">dialog</span><span class="nt">><button></span>Close<span class="nt"></button></form></span>
<span class="c"><!-- content and any other controls go here --></span>
<span class="nt"></dialog></span>
<span class="nt"><script></span>
<span class="kd">const</span> <span class="nx">b</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">b</span><span class="dl">'</span><span class="p">);</span>
<span class="kd">const</span> <span class="nx">d</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">dialog</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">b</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="p">()</span> <span class="o">=></span> <span class="p">{</span>
<span class="nx">d</span><span class="p">.</span><span class="nx">showModal</span><span class="p">();</span>
<span class="p">});</span>
<span class="nt"></script></span>
</code></pre></div></div>
<p>Followed up by “And if you can’t use the native <code class="language-plaintext highlighter-rouge">dialog</code> element, then use something like <a href="https://a11y-dialog.netlify.app/">a11y-dialog</a>.”</p>
<p>Now why would these be the best answers? Well, as mentioned I’ve written quite abit about that myself. You can go read up on those posts if you want. Otherwise, we can try asking ChatGPT about the accessibility of HTML’s dialog element?</p>
<p>Hmm… just tried that… but I’m tired of picking these responses apart, so, yeh. Maybe just take my word for it, please?</p>
<h2 id="the-end">The end</h2>
<p>I’m not going to poke at ChatGPT’s responses anymore. Because to be honest, these posts are meant as a warning <strong>for <em>right now</em></strong>, as in early 2023. I do expect this to get a lot better, with time. A lot of people are interested in AI and are making some significant efforts to use it, <em>and</em> to improve it. The people that have worked on this so far have done some pretty amazing things. I’m not that smart.</p>
<p>I just know a lot about web accessibility. Not everything. But a lot. ChatGPT only knows what it has learned from us. And collectively, right now, <em>we</em> don’t know a lot. So let’s go learn us some stuff, and then we can learn AI some stuff, too.</p>Scott O'HaraI’ve already talked about Setting expectations for asking ChatGPT web accessibility questions. So you should probably go read that first.Setting expectations for asking ChatGPT web accessibility questions2023-01-31T00:00:00+00:002023-01-31T00:00:00+00:00https://scottohara.me/blog/2023/01/31/ai-a11y-maybe-no<p>You may be familiar with <a href="https://chat.openai.com/chat">ChatGPT</a>. But if you’re not, it’s a very cool chat interface using artificial intelligence to provide human-like responses to the questions you ask it. It is, however, still rather new and depending on what you ask it… well… As the FAQ for ChatGPT explains:</p>
<blockquote>
<p>These models were trained on vast amounts of data from the internet written by humans, including conversations, so the responses it provides may sound human-like. It is important to keep in mind that this is a direct result of the system’s design (i.e. maximizing the similarity between outputs and the dataset the models were trained on) and that such outputs may be inaccurate, untruthful, and otherwise misleading at times.</p>
</blockquote>
<p>That’s some pretty important context to consider when reading what ChatGPT spits back at you. And, specifically in the context of what I want to prattle on about, you should probably not be relying on ChatGPT to write your accessible markup patterns for you, finding accessibility issues with the markup snippets you feed it, nor should you expect 100% factual and relavent answsers to your accessibility questions which likely require a level of nuance and understanding this AI <strong>reasonably</strong> doesn’t have yet.</p>
<p>I think their FAQ disclaimers make that clear, however…</p>
<h2 id="but-scott-ive-used-chatgpt-and-it-provides-some-accurate-information-notice-lack-of-specifics-in-this-statement">But Scott. I’ve used ChatGPT and it provides some accurate information (notice lack of specifics in this statement)!</h2>
<p>Cool! I’d expect it to, and I’m sure it can even provide you some really good information. That is, if you know what to ask and know how to interpret what it returns to you. My 10 year old can also provide some very detailed information about what I do for work. About half of what she would say is true, and the rest is generally vauge and plausible enough that, sure, I have trained a shark to help blind people read the web. Why not. My 7 year old just thinks I drink coffee and write emails. Again, partially true. I’m going off topic…</p>
<p>My point is that this technology is still rather young, and what it regurgitates to you is going to be based on what it’s learned from humans, who are also unfortunately often incorrect. Some may acknowledge these gaps, but still assume ChatGPT could provide people with at least baseline information to start learning about aspects of web accessibility. Or at least, provide you accurate code guidance to help build accessible websites.</p>
<p>As with the authenticity of my kids’ answers, there is some truth to that, so long as you know that this can give you a start, but you should be mindful and question each answer it provides you. Otherwise, if you don’t keep this in mind, and are not well informed on general web accessibility concepts, you’re likely going to be asking seemingly straight forward questions that the current state of this AI will not have the necessary context to answer. And, what it does provide you, will be returned in a way where it <em>seems like it would be accurate</em>, particularly for someone who is new to this information.</p>
<p>That’s a big difference between how AI like this presently function, vs how a real (reasonable) human might respond.</p>
<p>The AI is going to power through and give you <em>something</em> in the guise of an educated answer, even if that answer is smoke and mirrors. That’s not the intent of the functionality, but it’s a current reality we need to be aware of when reviewing the answers it gives us.</p>
<p>Let’s investigate.</p>
<h3 id="a-simple-question-or-the-easy-button">A ‘simple’ question. Or: “the ‘easy’ button”</h3>
<p>For starters, let’s look at the response it provided me when asking ChatGPT “What do I need to do to make an accessible button for my web page?” Purposefully a bit vague, but also that should be simple enough, right? Arguably it’s just as detailed as some accessibility questions I’ve been asked to answer…</p>
<p>When asked, it returned:</p>
<blockquote>
<p>To make an accessible button for your web page, follow these steps:</p>
<ol>
<li>Use the <code><button></code> element in your HTML.</li>
<li>Add a descriptive label to the button using the <code>aria-label</code> or <code>aria-labelledby</code> attribute.</li>
<li>Ensure keyboard accessibility by giving the button a tab index and using JavaScript to listen for keyboard events (such as "Enter" or "Spacebar") to trigger the button's action.</li>
<li>Use CSS to provide visual feedback for the button's state (e.g. hover, active, focus).</li>
<li>Test the button with assistive technologies such as screen readers to ensure it is usable and understandable.</li>
<li>If using an icon-only button, add a descriptive text alternative using <code>aria-hidden</code> and <code>aria-labelledby</code> attributes.</li>
<li>Consider additional accessibility best practices, such as making the button large enough to be easily tap-able on touch screens, and ensuring sufficient contrast between the button text and background color.</li>
</ol>
</blockquote>
<p>That sorta sounds reasonable enough? So, what’s my problem with this (because, I’m me - so of course I have a problem with this). Let’s dig into what it said.</p>
<h4 id="use-the-button-element-in-your-html">Use the <code class="language-plaintext highlighter-rouge"><button></code> element in your HTML.</h4>
<p>It starts off seemingly on the right track. To make an accessible button, use a <code class="language-plaintext highlighter-rouge"><button></code> element. Not a <code class="language-plaintext highlighter-rouge"><div></code>? OK, computer. You seem to be off to a good start. That is, so long as I can actually use a <code class="language-plaintext highlighter-rouge"><button></code> element. Or, maybe I’m green enough to say I want to make an accessible button, but really I’ve been tasked to take this design where this “button” is really a call to action link. But, these are early days, and ChatGPT has not been coded in a way to respond with “I know you asked this, but maybe you meant something else instead? You did also ask me about tooltips 46 times, and each time you were actually talking about a different UI pattern all together. Please do confirm your intent so I can learn you some stuff.”</p>
<p>I want a ChatGPT t-shirt that says, “Ask me anything so I can learn you some stuff” on it. Please.</p>
<h4 id="add-a-descriptive-label-to-the-button-using-the-aria-label-or-aria-labelledby-attribute">Add a descriptive label to the button using the <code class="language-plaintext highlighter-rouge">aria-label</code> or <code class="language-plaintext highlighter-rouge">aria-labelledby</code> attribute.</h4>
<p>The second bullet on the list, and we are being introduced to a lack of understanding and possible misinformation bias that “learning” from humans has brought upon this AI.</p>
<p>I say that because per the question asked, there was not enough information provided that this should have been mentioned without additional context. For a developer new to accessibility, this might seem legit. If you look these attributes up, you can verify they are used to provide elements an accessible name. If you look across the web, you can see ARIA sprinkled all over the place.</p>
<p>So what’s wrong?</p>
<p>In reality, <code class="language-plaintext highlighter-rouge">aria-label</code> or <code class="language-plaintext highlighter-rouge">aria-labelledby</code> are only necessary if a button does not have a visible label (for instance, a button with a graphic as its unique identifier), or its visible label is not descriptive enough.</p>
<p>For instance, if I were to implement a button that was used to edit a blog post, it could be written as such:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button></span>Edit<span class="nt"></button></span>
</code></pre></div></div>
<p>Brutalist in its simplicity. No need for ARIA at all. But, maybe I wanted to give it more context so that someone using a screen reader, and navigating specifically by buttons found on the web page, could more immediately understand <em>what</em> a specific button would edit, without needing to investigate other nearby content on the web page.</p>
<p>To provide that sort of context, I might do something like:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span> <span class="na">aria-label=</span><span class="s">"Edit [whatever this is about]"</span><span class="nt">></span>Edit<span class="nt"></button></span>
</code></pre></div></div>
<p>or</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><p</span> <span class="na">id=</span><span class="s">foo</span><span class="nt">></span>[whatever this is about]<span class="nt"></p></span>
<span class="nt"><button</span> <span class="na">id=</span><span class="s">self</span> <span class="na">aria-labelledby=</span><span class="s">"self foo"</span><span class="nt">></span>Edit<span class="nt"></button></span>
</code></pre></div></div>
<p>And that’s just if I wanted to use one of these attributes. There are other ways to do this, but, that’s a whole other topic that AI can scrape from the Internet another day.</p>
<p>Still though, what’s the harm in adding ARIA when you don’t need ARIA? That too depends on context.</p>
<p>For instance, maybe this button needs to be used on a web page where there is a widget to translate the page to another language. A common implementation gap I’ve seen time and time again is that the visible text of controls like this get updated with the translated string, but the <code class="language-plaintext highlighter-rouge">aria-label</code> doesn’t get updated. Oops.</p>
<p>But beyond buttons, there are certain HTML elements where if you needlessly provide them an <code class="language-plaintext highlighter-rouge">aria-label</code>, it can prevent a screen reader from accessing the nested content of that particular element. That’s not necessarily even a bug with ARIA, but rather the browser and screen reader assuming you know what you’re doing.</p>
<p>If you give an element, like a hyperlink, an <code class="language-plaintext highlighter-rouge">aria-label</code>, then you’re telling the screen reader that any content nested inside that link is of no interest. Is that always true? hmm…</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><a</span> <span class="na">href=</span><span class="s">"..."</span> <span class="na">aria-label=</span><span class="s">"concise title"</span> <span class="err">...</span><span class="nt">></span>
<span class="nt"><h</span><span class="na">#</span><span class="nt">></span>Actual title<span class="nt"></h</span><span class="err">#</span><span class="nt">></span>
<span class="nt"><p></span>Some more context that is visible, but LOL that aria-label<span class="nt"></p></span>
<span class="nt"><p></span>Some other stuff, cause why not. I'm a card or whatever. I do what I want.<span class="nt"></p></span>
<span class="nt"></a></span>
</code></pre></div></div>
<p>An awful lot of other content in there that is being overwritten by that concise title…</p>
<h4 id="ensure-keyboard-accessibility-by-giving-the-button-a-tab-index-and-using-javascript-to-listen-for-keyboard-events-such-as-enter-or-spacebar-to-trigger-the-buttons-action">Ensure keyboard accessibility by giving the button a tab index and using JavaScript to listen for keyboard events (such as “Enter” or “Spacebar”) to trigger the button’s action.</h4>
<p>More information that on its surface seems accurate, but is woefully unnecessary per the original guidance to use a <code class="language-plaintext highlighter-rouge"><button></code> element. A <code class="language-plaintext highlighter-rouge"><button></code> element is keyboard accessible by default. Giving it a “tab index” (rather, a <code class="language-plaintext highlighter-rouge">tabindex=0</code>) is useless advice at this point.</p>
<p>Additionally, while JavaScript will be necessary to setup an event listener for the button, a <code class="language-plaintext highlighter-rouge"><button></code> element already listens for the <kbd>Enter</kbd> and <kbd>Space</kbd> keys with the <code class="language-plaintext highlighter-rouge">click</code> event listener. So you definitely don’t want to take this instruction at face value. Otherwise, one could write the necessary JavaScript for a click, and key press events. And then, as one is likely to do, they’d not actually test to see how it works, because using a keyboard is hard. So then you’d get something like this:</p>
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="eYjLddr" data-user="scottohara" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/eYjLddr">
Untitled</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<h4 id="use-css-to-provide-visual-feedback-for-the-buttons-state-eg-hover-active-focus">Use CSS to provide visual feedback for the button’s state (e.g. hover, active, focus).</h4>
<p>Ok, no real problem with this one. It could have mentioned that one gets some of these states from the browser’s default styles - but let’s be serious, those are going to be overwriten by the design requirements. As far as a quick checkpoint on the list, it is so far the best advice after the simplistic “use the <code class="language-plaintext highlighter-rouge"><button></code> element”.</p>
<h4 id="test-the-button-with-assistive-technologies-such-as-screen-readers-to-ensure-it-is-usable-and-understandable">Test the button with assistive technologies such as screen readers to ensure it is usable and understandable.</h4>
<p>On its surface, seems like this could be appropriate, until you remember that the question demonstrates a potential lack of knowledge regarding web accessibility. I’d be very hesitant to introduce someone to the concept of flying a kite, and then following it up by letting them jump into the cockpit of an airplane.</p>
<h4 id="if-using-an-icon-only-button-add-a-descriptive-text-alternative-using-aria-hidden-and-aria-labelledby-attributes">If using an icon-only button, add a descriptive text alternative using <code class="language-plaintext highlighter-rouge">aria-hidden</code> and <code class="language-plaintext highlighter-rouge">aria-labelledby</code> attributes.</h4>
<p>This is a nice add, alluding to the nuance of what to do for buttons that are solely represented by graphics. However, it quickly follows up this initial bit of nuance by its hand wavy use of ARIA, before the guidance abruptly ends. Use <code class="language-plaintext highlighter-rouge">aria-hidden</code> and <code class="language-plaintext highlighter-rouge">aria-labelledby</code> on what now? I suppose we can ask ChatGPT what these attributes do…</p>
<h5 id="what-is-aria-hidden-for">what is aria-hidden for?</h5>
<blockquote>
<p><code class="language-plaintext highlighter-rouge">aria-hidden</code> is an ARIA attribute that indicates to screen readers if an element should be considered hidden or not. The value “true” means the element is hidden and should not be announced by screen readers, while “false” means the element should be announced by screen readers. This attribute is used to improve accessibility for visually impaired users.</p>
</blockquote>
<p>Clearly, ChatGPT is not faimilar with the various instances of flagrant misuse of this attribute. Otherwise, it would have known to stop short of saying “This attribute is used to improve accessibility for visually impaired users.”</p>
<p>Other than nits to the explanation aside, nothing too bad here. Moving on…</p>
<h4 id="consider-additional-accessibility-best-practices-such-as-making-the-button-large-enough-to-be-easily-tap-able-on-touch-screens-and-ensuring-sufficient-contrast-between-the-button-text-and-background-color">Consider additional accessibility best practices, such as making the button large enough to be easily tap-able on touch screens, and ensuring sufficient contrast between the button text and background color.</h4>
<p>And, we end on a positive note. No real detail, but nothing inaccurate here either. I actually don’t have any problem with this bit.</p>
<h2 id="that-was-an-easy-question">That was an “easy” question</h2>
<p>I had thought about writing about some of the responses ChatGPT was giving me for questions like: “how do I write an accessible hamburger menu, with react?” or “how do I make an accessible dialog?” - which then had to be clarified to “how do I make an accessible <em>MODAL</em> dialog”. Or, providing it the HTML of a small web page with known accessibility issues, and asking it to find the errors an automated checker should be able to find. All these questions/asks returned results with a mixture of truthful information, a bunch of other misinformation, and often a lot of missing important information.</p>
<p>However, I don’t see the point in digging into those more detailed asks. This is a really cool technology and it’s doing a lot in its relative infancy. I do expect this, and other AI-driven projects like it, to only get smarter, more nuanced, and produce better results in the future. But, it is going to need some corrective training to help it unlearn all the junk it has picked up so far. I mean, it’s learning from what <em>we’ve written on the Internet</em>. We can’t get upset, and we should KNOW to expect such information gaps and lack of understanding, since its just recycling what we have collectively fed it. It’d be foolish to think it knows better than what it has learned <em>from us</em>.</p>
<p>Maybe someday, after a lot of training, we can reasonably rely on AI-driven services like this to provide detailed, meaningful and nuanced answers to our accessibility questions. But, when the day comes that it responds to questions like the one I asked with: “You’re going to need to provide a lot more context than that if you want me to give you any sort of meaningful answer that isn’t a bunch of eneral information and platitudes”, well. That’s the day I can get back to my real job of sending emails and drinking coffee.</p>Scott O'HaraYou may be familiar with ChatGPT. But if you’re not, it’s a very cool chat interface using artificial intelligence to provide human-like responses to the questions you ask it. It is, however, still rather new and depending on what you ask it… well… As the FAQ for ChatGPT explains:Use the dialog element (reasonably)2023-01-26T00:00:00+00:002023-01-26T00:00:00+00:00https://scottohara.me/blog/2023/01/26/use-the-dialog-element<p>Today, the HTML specification landed a <a href="https://github.com/whatwg/html/commit/a9f103c9f7bd09ef712990194638c75db1f50e3c">very important change to the dialog element</a>. One that resolves a multi-year discussion on how the <code class="language-plaintext highlighter-rouge">dialog</code> element should handle initial focus.</p>
<p>As mentioned in the linked PR, the specific changes here are to:</p>
<ol>
<li>Make the <code class="language-plaintext highlighter-rouge">dialog</code> focusing steps look at sequentially focusable elements instead of any focusable element.</li>
<li>Make the <code class="language-plaintext highlighter-rouge">dialog</code> element itself get focus if it has the <code class="language-plaintext highlighter-rouge">autofocus</code> attribute set.</li>
<li>Make the <code class="language-plaintext highlighter-rouge">dialog</code> element itself get focus as a fallback instead of focus being reset to the <code class="language-plaintext highlighter-rouge">body</code> element.</li>
</ol>
<p>As this has just landed, it will take a bit more time for any of these changes (in the cases where they were not already implemented) to appear in browsers. But this is a good day for the <code class="language-plaintext highlighter-rouge">dialog</code> element.</p>
<p>As mentioned in the PR, <strong>there are still additional topics that need to be discussed and worked on</strong>. But, that’s also true for many HTML features. HTML is not perfect. That’s not a dig. Nothing’s perfect. And that’s why HTML, and other specifications, are constantly being worked on.</p>
<p>Instead of waiting for perfect, I <em>personally</em> think it’s time to move away from using custom dialogs, and to use the <code class="language-plaintext highlighter-rouge">dialog</code> element instead.</p>
<h2 id="use-it">Use it</h2>
<p><em>IMO</em>, the <code class="language-plaintext highlighter-rouge">dialog</code> element has reached the tipping point of <em>generally</em> being the better option for web developers who need to implement dialogs in their web pages. The number of accessibility requirements a developer needs to be aware of, and the level of effort to implement custom ARIA dialogs is now largely taken care of by browsers.</p>
<p>Will you still have use cases that would require a <code class="language-plaintext highlighter-rouge">role=dialog</code> over a <code class="language-plaintext highlighter-rouge">dialog</code> element? Yes.</p>
<p>Does your website or web application have its own requirements (user base, need to support legacy browsers, limited time/resources) that would make it unreasonable to migrate to using the <code class="language-plaintext highlighter-rouge">dialog</code> element? Sure!</p>
<p>But, if those - or other unmentioned reasons - don’t apply. Then it seems you’re a prime candidate to use the <code class="language-plaintext highlighter-rouge">dialog</code> element, with a minimal amount of JavaScript to implement. Versus adding an additional dependency (even if that <a href="https://github.com/KittyGiraudel/a11y-dialog">dependency is pretty darn great</a>), or deciding that someone else’s code doesn’t smell like you, so, let’s reinvent the wheel, again!</p>
<h2 id="dont-abuse-it">Don’t abuse it</h2>
<p>Now, let’s not get <a href="https://modalzmodalzmodalz.com/">reckless with dialogs</a>. This isn’t really advice that’s unique to the <code class="language-plaintext highlighter-rouge">dialog</code> element itself. People have a long history of taking elements/components that were intended for specific use cases, and then stressing them to their limit to fit <em>their</em> use case. Sometimes this makes sense, but other times (e.g., when your modal dialog consists of various headings, landmarks, and long-form content) it’s maybe best to sit back, take a deep breath, and think to yourself:</p>
<blockquote>
<p>Self. Hey. It's me, you know... you. I'm wondering. Maybe, just <em>maybe</em> we <strong>don't</strong> need to shove a web page into another web page by use of a smaller window atop the first web page.</p>
<p>...</p>
<p>I mean, we are already using an SPA. So ostensibly, even the argument that we need to use a modal dialog to save state, rather than going to a new web page, is a garbage lie we tell ourselves to maintain the way we've always done things, rather than accept the reality that we're absolutely willing to change course on other design and technical decisions due to a change in the wind from an opinionated topic within our community...</p>
<p>...</p>
<p>Oh F it, of course we need to keep shoving web pages into web pages! LOLOLOL! I LOVE MY DECISIONS!</p>
</blockquote>
<p>Well, that conversation didn’t go the way I had hoped… Anyway, yeh. Just because it’s easier to make dialogs now, than it has ever been, just please check yourself.</p>
<h2 id="report-issues--bugs">Report issues / bugs!</h2>
<p>To round this out, I’ll again make mention that there are still aspects of <code class="language-plaintext highlighter-rouge">dialog</code> which need work, or may even need to be implemented at all. For instance, non-modal (or modeless) dialogs? There’s some big question marks on how exactly that type of dialog should behave.</p>
<p>But, the best way to find out where there are still issues (functional, accessibility specific, feature requests, etc.) is to give the element a proper “go”.</p>
<p>So give the <code class="language-plaintext highlighter-rouge">dialog</code> a try. Be purposeful and reasonable with its use, keeping in mind any additional requirements that may limit or necessitate further testing and research (user, a11y, other) on your end.</p>
<p>Now if you’ll excuse me. I want to take a break from talking about dialogs for awhile (or at least, until its relevant to do so again) :)</p>Scott O'HaraToday, the HTML specification landed a very important change to the dialog element. One that resolves a multi-year discussion on how the dialog element should handle initial focus.Responsive accessibility using visibility hidden2022-11-07T00:00:00+00:002022-11-07T00:00:00+00:00https://scottohara.me/blog/2022/11/07/responsive-accessibility<p>Like it or not, even though content can be made to adapt to different viewport sizes and zoom levels by use of CSS media queries, sometimes the end result can be a bit awkward without further adjustments that go beyond visual presentation.</p>
<p>Without getting too into it, sometimes content that may appear in the normal flow of a web page may need to instead be rendered as a dialog on smaller viewports. Or, UI which shows/hides may make sense on smaller viewports but does not need to be treated this way when a larger viewport is available.</p>
<p>While HTML provides features, such as the <code class="language-plaintext highlighter-rouge">source</code> element’s <code class="language-plaintext highlighter-rouge">media</code> attribute, where the <code class="language-plaintext highlighter-rouge">source</code> for a <code class="language-plaintext highlighter-rouge">picture</code> element will be determined based on a media query breakpoint, this capability doesn’t extend to other HTML elements.</p>
<p>Maybe something like it should?</p>
<p>I have a few use cases for this, but let’s just look at one for now.</p>
<h2 id="media-queries-to-toggle-toggles">Media queries to toggle toggles</h2>
<p>Imagine you have an FAQ page where on large screen all the commonly asked questions which unfortunately are not obvious enough to figure out are fully displayed with their answers for people to read. On small screen, however, in an effort to help someone be able to find the question they want answered with a reduced amount of scrolling, all the answers have been collapsed by default.</p>
<p>So on large screen we have a web page that is a series of headings followed by their answers, which would have a markup pattern like so:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><h</span><span class="na">#</span><span class="nt">></span>
Frequently asked question
<span class="nt"></h</span><span class="err">#</span><span class="nt">></span>
<span class="nt"><div></span>Answer goes here!<span class="nt"></div></span>
</code></pre></div></div>
<p>But on small screen, a pattern like the following would be expected:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><h</span><span class="na">#</span><span class="nt">></span>
<span class="nt"><button</span> <span class="na">aria-expanded=</span><span class="s">"..."</span><span class="nt">></span>Frequently asked question<span class="nt"></button></span>
<span class="nt"></h</span><span class="err">#</span><span class="nt">></span>
<span class="nt"><div></span>Answer goes here!<span class="nt"></div></span>
</code></pre></div></div>
<p>Now, we don’t want to have to redo the DOM depending on the viewport size. That’d be annoying. We also can’t just style away the visual presentation of the button on larger screens, because the underlying implicit and explicit ARIA semantics would still be exposed to people using assistive technology. And, the button functionality and focusability would still remain, regardless of styling.</p>
<p>Again, one might think to use JavaScript to potentially add/remove the button based on viewport size. Or, maybe because that’s super annoying, one could add remove ARIA and HTML attributes to the button(s) to suppress the semantics and behaviors. But that too would be annoying and you’d end up with some garbage markup at large screen that resembled the following:</p>
<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code><h#>
<button aria-expanded="..."
disabled
role="none"
>Frequently asked question</button>
</h#>
<div>Answer goes here!</div>
</code></pre></div></div>
<p>Where the <code class="language-plaintext highlighter-rouge">disabled</code> attribute would need to be applied to completely suppress the <code class="language-plaintext highlighter-rouge">button</code> element’s focusability (<code class="language-plaintext highlighter-rouge">tabindex=-1</code> would only remove it from the focus order of the page, but would still mean it was focusable, after all). Styling would need to be done to make the now <code class="language-plaintext highlighter-rouge">disabled</code> button not <em>look</em> disabled. The <code class="language-plaintext highlighter-rouge">role=none</code> would then need to be applied to the button so that its implicit button role and disabled state were no longer communicated.</p>
<p>That’s annoying and even though it’s not adding and removing a <code class="language-plaintext highlighter-rouge">button</code> element, it is potentially a lot of DOM manipulation.</p>
<h3 id="using-css-and-visibility">Using CSS and <code class="language-plaintext highlighter-rouge">visibility</code></h3>
<p>Instead of modifying the DOM, we could instead write the some CSS to show/hide the <code class="language-plaintext highlighter-rouge">button</code> element based on the necessary media query breakpoints.</p>
<p>Consider the following:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><h3></span>
<span class="nt"><button</span> <span class="na">aria-expanded=</span><span class="s">...</span><span class="nt">></span>
<span class="nt"><span></span>My text<span class="nt"></span></span>
<span class="nt"></button></span>
<span class="nt"></h3></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">content</span><span class="nt">></span>
the content that toggles depending on breakpoint.
<span class="nt"></div></span>
</code></pre></div></div>
<p>Not too different than the small screen markup needed for setting up the disclosure widgets, but here there’s a new <code class="language-plaintext highlighter-rouge">span</code> element that contains the <code class="language-plaintext highlighter-rouge">button</code> element’s content. Why?</p>
<p>Well, this is where <a href="https://developer.mozilla.org/en-US/docs/web/css/visibility">CSS’s <code class="language-plaintext highlighter-rouge">visibility</code> property</a> comes into play.</p>
<p>The <code class="language-plaintext highlighter-rouge">visibility</code> property is an interesting one. When applied to an element with the <code class="language-plaintext highlighter-rouge">hidden</code> value, it will result in the element and its contents being visually hidden, though it still takes up the “space” that it would in the rendered web page. It’s just like one big visual hole and is similar to if you had applied <code class="language-plaintext highlighter-rouge">opacity: 0</code> to that same content. However, unlike with <code class="language-plaintext highlighter-rouge">opacity</code>, <code class="language-plaintext highlighter-rouge">visibility: hidden</code> will result in that content <strong>not</strong> being exposed by the browser’s accessibility API (i.e., it won’t show up in the a11y tree). This is similar behavior to <code class="language-plaintext highlighter-rouge">display: none</code> which also hides content from the a11y tree.</p>
<p>What makes <code class="language-plaintext highlighter-rouge">visibility</code> even more different, and <em>useful</em> here, is that you can make parts of a <code class="language-plaintext highlighter-rouge">visibility: hidden</code> subtree visible again.</p>
<p>Let’s now look at this in context of the markup pattern for the FAQ show/hide buttons.</p>
<p>At large screen viewports the <code class="language-plaintext highlighter-rouge">button</code> element needs to be hidden. But, the text within needs to be visible at all viewports. So if we were do to the following:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="k">@media</span> <span class="n">screen</span> <span class="n">and</span> <span class="p">(</span> <span class="n">min-width</span><span class="p">:</span> <span class="m">600px</span> <span class="p">)</span> <span class="p">{</span>
<span class="nt">button</span><span class="o">[</span><span class="nt">aria-expanded</span><span class="o">]</span> <span class="p">{</span>
<span class="nl">visibility</span><span class="p">:</span> <span class="nb">hidden</span><span class="p">;</span>
<span class="p">}</span>
<span class="p">}</span>
<span class="nt">button</span><span class="o">[</span><span class="nt">aria-expanded</span><span class="o">]</span> <span class="nt">span</span> <span class="p">{</span>
<span class="nl">visibility</span><span class="p">:</span> <span class="nb">visible</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The first ruleset within the media query will ensure that the <code class="language-plaintext highlighter-rouge">button</code> elements with the <code class="language-plaintext highlighter-rouge">aria-expanded</code> attribute will be hidden at viewport sizes larger than 600px. Ensuring that the <code class="language-plaintext highlighter-rouge">button</code> element will not be exposed.</p>
<p>The second ruleset will ensure that the subtree of the button, contained within the <code class="language-plaintext highlighter-rouge">span</code> element, will always be shown regardless of the <code class="language-plaintext highlighter-rouge">button</code> element being set to <code class="language-plaintext highlighter-rouge">visibility: hidden</code> or not.</p>
<p>With some additional styles and JavaScript to properly setup the disclosure widget (hey, <a href="https://www.scottohara.me/blog/2022/09/12/details-summary.html#creating-an-aria-disclosure-widget">I wrote something about that</a>), you can get the following:</p>
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="abKBvMP" data-user="scottohara" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/abKBvMP">
responsive toggle button</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<h2 id="isnt-this-a-little-hacky--confusing-though">Isn’t this a little hacky / confusing though?</h2>
<p>It sure is. But right now we don’t have a particularly good solution to do this without leaning too heavily on JavaScript or potentially even CSS to do things that really probably belong as a native HTML feature so that stuff like this can even work if JavaScript or CSS are not available. E.g., when these extra resources are blocked or fail to load for some reason or another. Whether that be through a problem with the server, or someone purposefully turning on reader mode for their browser and all author defined CSS and JS are kicked to the curb.</p>
<p>Extra effort would need to be taken (likely with JavaScript) to make this behave in a nice progressively enhanced sort of way. But, I’m content with just being clever today. Maybe I, or someone else, can figure out how to make this a smart idea some other time.</p>Scott O'HaraLike it or not, even though content can be made to adapt to different viewport sizes and zoom levels by use of CSS media queries, sometimes the end result can be a bit awkward without further adjustments that go beyond visual presentation.The details and summary elements, again2022-09-12T00:00:00+00:002022-09-12T00:00:00+00:00https://scottohara.me/blog/2022/09/12/details-summary<div role="note">
<p>
I've already written about the
<a href="https://www.scottohara.me/blog/2018/09/03/details-and-summary.html"><code><details></code> and <code><summary></code> elements</a>, but there is more to tell, and there have been some changes since I last wrote at length about them in 2018.</p>
</div>
<h2 id="details-and-summary"><code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code></h2>
<p>HTML’s <a href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-details-element"><code class="language-plaintext highlighter-rouge"><details></code></a> and <a href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-summary-element"><code class="language-plaintext highlighter-rouge"><summary></code></a> elements can be used to create native disclosure widgets - an expand/collapse widget where more information or interactive controls related to a topic (identified via the subtree content of the <code class="language-plaintext highlighter-rouge"><summary></code>, or text preceding the disclosure widget) can be found. These native disclosure widgets function without JavaScript, unless you need to support legacy browsers. Yes, IE11 is no longer supported, but that doesn’t mean it’s been erased from existence. Do what’s necessary for your users.</p>
<p>HTML classifies both elements as <a href="https://html.spec.whatwg.org/multipage/interactive-elements.html#interactive-elements">“interactive elements”</a>. The <code class="language-plaintext highlighter-rouge"><details></code> element serves as the containing element for the widget, though it is not expected to be directly interacted with by users. Nor is <code class="language-plaintext highlighter-rouge"><details></code> generally expected to be exposed to assistive technologies. The <a href="https://html.spec.whatwg.org/multipage/interactive-elements.html#attr-details-open"><code class="language-plaintext highlighter-rouge">open</code> attribute</a> of the <code class="language-plaintext highlighter-rouge"><details></code> element is used to modify the expanded or collapsed state of the widget. When present it represents the widget being in the expanded state, and when absent the collapsed state. While the attribute is used on the <code class="language-plaintext highlighter-rouge"><details></code> element, the state is programmatically communicated by its <a href="#required-summary">required <code class="language-plaintext highlighter-rouge"><summary></code> element</a>.</p>
<p>The <code class="language-plaintext highlighter-rouge"><summary></code> element, the only child content of the <code class="language-plaintext highlighter-rouge"><details></code> that will be persistently visible regardless of the widget’s state, acts as the disclosure widget trigger. It serves as its “legend” or “caption” as a means to represent the further contents of the <code class="language-plaintext highlighter-rouge"><details></code> element. Interacting with the element will toggle the <code class="language-plaintext highlighter-rouge">open</code> attribute of its parent <code class="language-plaintext highlighter-rouge"><details></code>. Generally, the <code class="language-plaintext highlighter-rouge"><summary></code> element will expose an “expanded” or “collapsed” state, correlating to the <code class="language-plaintext highlighter-rouge">open</code> attribute existing or not on the parent <code class="language-plaintext highlighter-rouge"><details></code>, and thus programmatically conveying whether the associated content is shown or hidden.</p>
<p>The <code class="language-plaintext highlighter-rouge"><summary></code> element is ‘supposed to be’ exposed to assistive technologies as having a “<code class="language-plaintext highlighter-rouge">button</code>” role. However, its role may also be announced as “summary”, or even as a “disclosure triangle” because that’s a choice someone made on purpose… and even sometimes (unfortunately) no role at all. How it is exposed is contingent on the platform/browser and screen reader pairing. More on this later.</p>
<p>The following is an example of a <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code>.</p>
<details open="">
<summary>Learn how the <code><summary></code> element is exposed to AT:</summary>
<div>
<p>
Depending on the screen reader and browser pairing you may be using, the <code><summary></code> element would have been exposed in one of the following ways:</p>
<ul>
<li>"Disclosure Triangle" with Narrator, VoiceOver and TalkBack when paired with Edge/Chrome.</li>
<li>"Button" with NVDA when paired with Firefox, Edge or Chrome.</li>
<li>"Summary" with VoiceOver when paired with Firefox or Safari.</li>
<li>Bugged behavior or no role announced with TalkBack with Firefox or iOS VO with Safari, respectively.</li>
</ul>
<p>
These results and more can be found <a href="https://codepen.io/scottohara/pen/aaJXYG">with this CodePen</a>.
</p>
</div>
</details>
<p>The simplified markup of the previous disclosure widget is as follows:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><details</span> <span class="na">open</span><span class="nt">></span>
<span class="nt"><summary></span>...<span class="nt"></summary></span>
<span class="nt"><div></span>
<span class="nt"><p></span>...<span class="nt"></p></span>
...
<span class="nt"></div></span>
<span class="nt"></details></span>
</code></pre></div></div>
<div role="note">
<p>You <em>don't need</em> to have a <code><div></code> as a sibling element to the <code><summary></code>. I just like to add it for styling purposes. <a href="https://www.scottohara.me/blog/2022/01/20/divisive.html"><code><div></code>s are fine</a> if used appropriately, after all.</p>
</div>
<h3 id="required-summary">
A summary, whether you want it or not
</h3>
<p>Browsers will automatically add a <code><summary></code> element as the first child element of a <code><details></code> if it was not explicitly added. The subtree for this overlooked <code><summary></code> would contain the localized fallback text of “details” (which would be the <code><summary></code> element’s accessible name). The following disclosure widget demonstrates this error correction:</p>
<details>
Oh hi there. Forget your summary, didja?
</details>
<p>This fallback expectation is mentioned in the definition for the <a href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-details-element"><code><details></code> element</a> in the HTML specification, as well as in the <a href="https://w3c.github.io/html-aam/#summary-element-accessible-name-computation">HTML AAM accessible name calculation steps</a>.</p>
<p>Additionally, a <code class="language-plaintext highlighter-rouge"><summary></code> is supposed to be the first child element of a <code class="language-plaintext highlighter-rouge"><details></code>. However, if you were to do something like the following:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><details></span>
I'm putting content here even though the summary
is supposed to be the first element.
<span class="nt"><summary></span>
I'm out of place, but I won't stay that way
<span class="nt"></summary></span>
<span class="nt"></details></span>
</code></pre></div></div>
<p>Browsers will render the <code class="language-plaintext highlighter-rouge"><summary></code> as the first element anyway, mitigating one’s inability to follow very simple markup instructions.</p>
<h3 id="styling-a-disclosure-widget">Styling a disclosure widget</h3>
<p>The <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code> elements are fairly flexible with their styling. You can even remove the UA styling for the default triangle icon.</p>
<p>This would be done via two selectors:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">summary</span><span class="nd">::-webkit-details-marker</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="p">}</span>
<span class="nt">summary</span> <span class="p">{</span>
<span class="nl">list-style</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<p>Then, for example, a new icon to visually indicate state could be added via pseudo-elements, or adding in your own image, or whatever. You do you. Just remember that the <code class="language-plaintext highlighter-rouge"><summary></code> already (mostly) indicates the expand/collapse state. So if you use an inline graphic (image, SVG, font-icon, whatever), it <em>should</em> be treated as decorative. More on this later.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><details></span>
<span class="nt"><summary><img</span> <span class="na">src=</span><span class="s">my-icon.png</span> <span class="na">alt</span><span class="nt">></span> ...<span class="nt"></summary></span>
...
<span class="nt"></details></span>
</code></pre></div></div>
<p>Using the <code class="language-plaintext highlighter-rouge">open</code> attribute, one can create styles specific to the expanded and collapsed state of disclosure widget. For instance:</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">details</span> <span class="o">></span> <span class="nt">summary</span> <span class="p">{</span>
<span class="c">/* closed styles as necessary */</span>
<span class="p">}</span>
<span class="nt">details</span><span class="o">[</span><span class="nt">open</span><span class="o">]</span> <span class="o">></span> <span class="nt">summary</span> <span class="p">{</span>
<span class="c">/* opened styles as necessary */</span>
<span class="p">}</span>
</code></pre></div></div>
<p>The following is a quick example of how one could use the <code class="language-plaintext highlighter-rouge">summary::after</code> pseudo-element selector to create a CSS-only state indicator.</p>
<p class="codepen" data-height="488.41796875" data-default-tab="html,result" data-slug-hash="vYWvWEY" data-user="scottohara" style="height: 488.41796875px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/vYWvWEY">
Impact of styling a summary element?</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<h4 id="impact-of-removing-the-default-disclosure-triangle">Impact of removing the default disclosure triangle</h4>
<p>It’s worth noting that with Firefox in particular, it exposes the default disclosure widget triangle marker as part of the <code class="language-plaintext highlighter-rouge"><summary></code>’s accessible name. This can lead to some ‘interesting’ (ridiculous) announcements such as “black pointing small triangle” being part of the element’s accessible name. And <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=1783925">here’s the Firefox bug</a> I filed for this silliness.</p>
<p>If removing this marker so you can insert your own custom chevron or other visual expanded/collapsed state marker, it will actually make the state of the widget unclear with Firefox and VoiceOver, since the announcement of the default triangle direction <strong>is the only way state is communicated in that pairing</strong>.</p>
<p>Cool.</p>
<p>However, Firefox and VoiceOver alone is not the only pairing that is impacted by the removal of the default triangle marker. VoiceOver, JAWS and NVDA all have an issue with consistently announcing the toggled state of the disclosure widget if this marker is removed.</p>
<p>Something to keep in mind as you determine just how important it is to restyle an element whose semantics are, at least presently, semi-reliant on their default styling.</p>
<h3 id="special-find-in-page-behavior">Special find-in-page behavior</h3>
<p>Previously, when a <code class="language-plaintext highlighter-rouge"><details></code> element lacked the <code class="language-plaintext highlighter-rouge">open</code> attribute, all of its descendant elements, except for the <code class="language-plaintext highlighter-rouge"><summary></code> element, would be completely hidden to all users. That has changed. Now, if a user performs a find-in-page (e.g., <kbd>Ctrl</kbd> or <kbd>Command</kbd> + <kbd>F</kbd> keys) when using a Chromium browser, then the content of these disclosure widgets can become discoverable.</p>
<p>Give it a try. Search for the word “heyo” if you’re using Chrome or Edge. The following <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code> will automatically open:</p>
<details>
<summary>Search for "heyo" instead of toggling me!</summary>
<p>
Heyo! You found me (maybe... 'it depends™').
</p>
</details>
<p>A common criticism of disclosure widgets is that they make the discoverability of information difficult, and this find-in-page reveal helps mitigate that criticism. It would be nice to see this get implemented in Firefox and Safari as well.</p>
<div role="note">
<p>Consider this find-in-page feature before thinking you might use a <code><details></code> and <code><summary></code> as the foundation for other command-centric (navigation or menus) or dialog (especially modal) components. You probably don't want a disclosure widget of menu items, or a custom modal dialog to randomly open when performing an in-page-search.</p>
<p>
Granted, you can get around this by making sure the non-<code><summary></code> contents of the customized <code><details></code> are set to <code>display: none</code> or <code>visibility: hidden</code>. I mean, with enough time and effort, one <strong>can</strong> suppress enough of the native functionality and implicit ARIA semantics of the <code><details></code> and <code><summary></code> elements, and mold them into some other component entirely. But, generally the amount of effort this entails can be equal to or greater than just creating a more traditional JS-based control, or using other HTML features like <a href="https://www.scottohara.me/blog/2019/03/05/open-dialog.html">the <code><dialog></code> element</a>.
</p>
</div>
<h3 id="oddities-and-further-accessibility-information">Oddities and further accessibility information</h3>
<p>It may be of little surprise to you that the <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code> elements do have accessibility quirks, especially since I already talked about some of them. But being aware of these can help you avoid possible accessibility issues. Some of these quirks are a result of misuse/bending of the element’s purpose, while some are surfaced due to <em>valid use</em> of the elements.</p>
<h4 id="consistent-role-announcements-just-be-aware">Consistent role announcements? Just be aware…</h4>
<p>As mentioned, the implicit ARIA semantics for the <code class="language-plaintext highlighter-rouge"><summary></code> element (e.g., the role that is exposed) is a bit varied. One might think to “fix” this by specifying a <code class="language-plaintext highlighter-rouge">role=button</code> on the <code class="language-plaintext highlighter-rouge"><summary></code> element. Doing so would forcibly expose it as a button on all platforms.</p>
<p>However, if one were to do that, macOS Safari would treat the element as a standard button and the expanded/collapsed state would be dropped. This would also impact any instance that had a heading nested within the <code class="language-plaintext highlighter-rouge"><summary></code> element. The <code class="language-plaintext highlighter-rouge">role=button</code> of the <code class="language-plaintext highlighter-rouge"><summary></code> element would cause the nested heading semantics to be dropped with macOS Safari. Adding the role would at least make iOS VO announce <strong>a role</strong> for the <code class="language-plaintext highlighter-rouge"><summary></code> element, but would still do nothing to mitigate the lack of iOS announcing state.</p>
<p>If your goal is to create an absolutely consistent disclosure widget behavior across browsers, i.e., ensuring that all <code class="language-plaintext highlighter-rouge"><summary></code>s are exposed as expand/collapse buttons, then you’d be better off creating your own using JavaScript and the necessary ARIA attributes. You’d lose out on the find-in-page functionality that the native disclosure widget provides… but depending on the type of disclosure widget you’re creating, you may actually want that. You can check out my <a href="https://github.com/scottaohara/aria_disclosure_widget">GitHub repo for an ARIA disclosure widget</a> I created, or check out the examples on the <a href="https://scottaohara.github.io/aria_disclosure_widget/">demo page</a>. Or, go look at <a href="https://www.w3.org/WAI/ARIA/apg/patterns/disclosure/">the ARIA Authoring Practices disclosure widget examples</a>. Or, the tons of other show/hide buttons that people have made over the years.</p>
<div role="note">
<p>In the future the `hidden` attribute will hopefully (likely?) have wider support for the <code>until-found</code> value. This would allow authors to effectively create native <code><details></code> and <code><summary></code> behavior with ARIA and JS-powered disclosure widgets, if they so chose.</p>
</div>
<h4 id="giving-detail-to-details">Giving detail to <code class="language-plaintext highlighter-rouge"><details></code></h4>
<p>As mentioned, the <code class="language-plaintext highlighter-rouge"><details></code> element isn’t really meant to be exposed or interacted with directly. But hey, maybe you have different ideas?</p>
<p>Well, if you <a href="https://codepen.io/scottohara/pen/ExogpRN">decided to name a <code class="language-plaintext highlighter-rouge"><details></code> element</a>, you’d generally find that a <code class="language-plaintext highlighter-rouge">details</code> or <code class="language-plaintext highlighter-rouge">group</code> role would be exposed (note one of those is a valid ARIA role, and the other is merely the element’s tag name being conveyed as its role). Naming the <code class="language-plaintext highlighter-rouge"><details></code> would result in no other noticeable impact to the <code class="language-plaintext highlighter-rouge"><summary></code> element’s exposure to the tested screen readers. With TalkBack and Edge/Chrome, the name, let alone the existence of the details/group role was not announced. That’s not a big deal though… especially since naming the <code class="language-plaintext highlighter-rouge"><details></code> element is something you probably shouldn’t be doing anyway.</p>
<p>Where things get more quirky is if you <a href="https://codepen.io/scottohara/pen/VwrqrOm">want to change the implicit role of the <code class="language-plaintext highlighter-rouge"><details></code> element</a>. This has a direct impact on how the <code class="language-plaintext highlighter-rouge"><summary></code> element’s role will be exposed (spoiler, it generally won’t be).</p>
<p class="codepen" data-height="612" data-default-tab="html,result" data-slug-hash="VwrqrOm" data-user="scottohara" style="height: 612px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/VwrqrOm">
A details element by any other role</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<p>This is something that Firefox should probably get to fixing. But, it also begs the question why someone couldn’t just do:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!-- look ma', no ARIA! --></span>
<span class="nt"><article></span>
<span class="nt"><details></span>
<span class="nt"><summary></span>...<span class="nt"></summary></span>
...
<span class="nt"></details></span>
<span class="nt"></article></span>
</code></pre></div></div>
<p>You know… don’t use ARIA if you don’t need to? I think <a href="https://www.w3.org/TR/using-aria/#rule1">someone wrote about that somewhere</a>.</p>
<h4 id="invalid-summary">Invalid <code class="language-plaintext highlighter-rouge"><summary></code></h4>
<p>The great thing about browsers is that they will correct for many author errors to help ensure that a web page will render, regardless of invalid HTML markup. The not so great thing about browsers is that they will correct for many author errors to help ensure that a web page will still render, regardless of invalid HTML markup.</p>
<p>A <code class="language-plaintext highlighter-rouge"><summary></code> element is only valid as a child of a <code class="language-plaintext highlighter-rouge"><details></code> element. Additionally, only a single <code class="language-plaintext highlighter-rouge"><summary></code> is valid within a <code class="language-plaintext highlighter-rouge"><details></code> element. However, if a web developer were to do the following (<em>and they have</em>):</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><details></span>
<span class="nt"><summary></span>I'm valid<span class="nt"></summary></span>
<span class="nt"><summary></span>I'm not... why the hell am I here?<span class="nt"></summary></span>
...
<span class="nt"></details></span>
<span class="c"><!-- or --></span>
<span class="nt"><div></span>
<span class="nt"><summary></span>Sumdivery?<span class="nt"></summary></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>Browsers would parse the above, silently groan (yes, browsers judge you) about how often people mess up the fundamental programming language of the web, and then mitigate for this lack of knowledge and figure out the best way to render the invalid content. For instance:</p>
<figure aria-label="Example of rendering invalid summary elements">
<figcaption>
<p>The following demonstrates the output of the invalid markup in the previous code snippet.</p>
</figcaption>
<details>
<summary>I'm valid</summary>
<summary>I'm not... why the hell am I here?</summary>
...
</details>
<!-- or -->
<div>
<summary>Sumdivery?</summary>
</div>
</figure>
<p>Browsers correct for the double <code class="language-plaintext highlighter-rouge"><summary></code> elements within the <code class="language-plaintext highlighter-rouge"><details></code> by only using the first <code class="language-plaintext highlighter-rouge"><summary></code> for its intended purpose, and then including the second element as a part of the collapsible content of the widget.</p>
<p>Both this secondary <code class="language-plaintext highlighter-rouge"><summary></code>, and the one placed outside the context of a <code class="language-plaintext highlighter-rouge"><details></code> are visually rendered as static text and may still be rendered with a list marker, making it appear as a single item within a list.</p>
<p>Oh. But, I said “visually rendered” there. That was important.</p>
<p>In Chromium browsers the errant <code class="language-plaintext highlighter-rouge"><summary></code> is exposed as a <code class="language-plaintext highlighter-rouge">generic</code> element. Which makes sense since it is not an interactive element.</p>
<p>Firefox and Safari, however, do not presently modify the exposed role of the element. So, while the <code class="language-plaintext highlighter-rouge"><summary></code> is no longer keyboard accessible, AT will still expose it as if it was an interactive element. <a href="https://github.com/w3c/html-aam/pull/436">This HTML AAM PR</a> and the browser bugs linked within will hopefully rectify this relatively soon.</p>
<h4 id="summary-can-have-nested-interactive-elements-and-headings"><code class="language-plaintext highlighter-rouge"><summary></code> can have nested interactive elements and headings</h4>
<p>So if you’ve heard anything about accessibility quirks with <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code> elements, this is probably the topic that gets brought up the most. The HTML specification indicates the allowed content model for <a href="https://html.spec.whatwg.org/multipage/interactive-elements.html#the-summary-element">the <code class="language-plaintext highlighter-rouge"><summary></code> element</a> allows for <a href="https://html.spec.whatwg.org/multipage/dom.html#phrasing-content-2">phrasing content</a> with optionally intermixed <a href="https://html.spec.whatwg.org/multipage/dom.html#heading-content-2">heading content</a>.</p>
<p>Phrasing content includes various interactive elements such as <code class="language-plaintext highlighter-rouge"><input></code>, <code class="language-plaintext highlighter-rouge"><textarea></code>, <code class="language-plaintext highlighter-rouge"><button></code>, <code class="language-plaintext highlighter-rouge"><a href></code>, etc., and heading content - as the name would suggest - includes <code class="language-plaintext highlighter-rouge"><h1></code> to <code class="language-plaintext highlighter-rouge"><h6></code> elements, as well as <code class="language-plaintext highlighter-rouge"><hgroup></code>.</p>
<p>While HTML allows such elements within a <code class="language-plaintext highlighter-rouge"><summary></code>, there is an <a href="https://github.com/whatwg/html/issues/2272">open issue about whether or not it should</a>. Since I’ve responded to that issue with a wall of text, I don’t think I need to re-get into it here. But the gist is that, as mentioned, <code class="language-plaintext highlighter-rouge"><summary></code> element mappings are messy and part of the reason why nested headings and interactive elements are not consistently exposed. Safari, Firefox and Chromium browsers all treat nested interactive elements a bit differently - as outside their use in <code class="language-plaintext highlighter-rouge"><summary></code> elements, it’s invalid HTML to nest them. And because <code class="language-plaintext highlighter-rouge"><button></code> elements - or elements like <code class="language-plaintext highlighter-rouge"><summary></code> that are supposed to map to <code class="language-plaintext highlighter-rouge">role=button</code> are <em>supposed</em> to treat child elements as presentational, this is why headings or other elements with semantics important to accessibility are not consistently exposed.</p>
<p>It’s honestly a very messy issue with pros and cons all over the place concerning what could be done. We’ll just have to see how that all pans out and in the meantime, people should just realize that just because something is allowed in HTML, that doesn’t mean it’s going to be a good idea for all users. As another example, allowing <code class="language-plaintext highlighter-rouge"><a href></code> elements to wrap all sorts of things. I’m not going to even get started on how awful that can be…</p>
<h2 id="creating-an-aria-disclosure-widget">Creating an ARIA disclosure widget</h2>
<p>OK, so you’ve decided that you want to roll your own disclosure widget because you’re now fully up to speed on how <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code> are weirdos with some issues they need to work out.</p>
<p>To create your own custom disclosure widget, you would essentially need a <code class="language-plaintext highlighter-rouge"><button></code> element, an element to serve as your content “container” and then a few ARIA attributes to communicate the expanded and collapsed state.</p>
<p>For instance, a very basic widget could be made with the following markup:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span>
<span class="na">type=</span><span class="s">button</span>
<span class="na">aria-expanded=</span><span class="s">true|false</span>
<span class="nt">></span>
My trigger's name
<span class="nt"></button></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">"dw-panel"</span><span class="nt">></span>
<span class="c"><!-- content goes here! --></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>A minimal script to toggle the content would be as follows:</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">dwBtn</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">querySelector</span><span class="p">(</span><span class="dl">'</span><span class="s1">button[aria-expanded]</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">dwBtn</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">click</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span><span class="p">()</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="k">this</span><span class="p">.</span><span class="nx">getAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">aria-expanded</span><span class="dl">'</span><span class="p">)</span> <span class="o">===</span> <span class="dl">'</span><span class="s1">true</span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">aria-expanded</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">false</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="k">else</span> <span class="p">{</span>
<span class="k">this</span><span class="p">.</span><span class="nx">setAttribute</span><span class="p">(</span><span class="dl">'</span><span class="s1">aria-expanded</span><span class="dl">'</span><span class="p">,</span> <span class="dl">'</span><span class="s1">true</span><span class="dl">'</span><span class="p">);</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>Being a custom widget, styling is also as intricate or bare bones as you require. Per the above markup and scripted behavior, and without introducing anymore complexity than necessary, the following style selectors can be used and expanded upon.</p>
<div class="language-css highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt">button</span><span class="o">[</span><span class="nt">aria-expanded</span><span class="o">]</span> <span class="p">{</span>
<span class="c">/* style as you see fit */</span>
<span class="p">}</span>
<span class="nt">button</span><span class="o">[</span><span class="nt">aria-expanded</span><span class="o">=</span><span class="nt">false</span><span class="o">]</span> <span class="o">~</span> <span class="nc">.dw-panel</span> <span class="p">{</span>
<span class="nl">display</span><span class="p">:</span> <span class="nb">none</span><span class="p">;</span>
<span class="p">}</span>
</code></pre></div></div>
<div role="note">
<h3>An important note about rolling your own disclosure widget!</h3>
<p>Depending on the situations your disclosure widget might be used in, you should strongly consider creating such components with a progressive enhancement mindset.</p>
<p>For instance, if you are going to use disclosure widgets on a web page that a user could parse via a browser's reader mode, then you should ensure that your <code><button></code> and especially its <code>aria-expanded</code> state are provided by your JavaScript.</p>
<p>The reason for this is because in a browser's reader mode, all your styling and JavaScript is dropped, but your markup generally remains. So if you have a hard coded <code><button aria-expanded=false></code>, then that can still be presented in the reader mode version of your web page.</p>
<p>What is especially important here is also to avoid hard coding the <code>hidden</code> attribute for your custom disclosure widgets. It too will be respected in reader mode, so if used on your disclosure widget content panels, then that content will not render in reader mode.</p>
<p>The <code><details></code> and <code><summary></code> elements <strong>continue to function</strong> in a browser's reader mode.</p>
<p>Also, notice the lack of <code>aria-hidden=true</code> on the containing element for the content that shows/hides. <code>display: none</code> already hides the content from assistive technologies, which would mean using <code>aria-hidden=true</code> on the <code>.dw-panel</code> would not only be useless, but introduce a potential for inaccessible content were the CSS of the page to be blocked somehow, as then the content would be visible, but not readable to assistive technologies.</p>
</div>
<h3 id="other-disclosure-widget-options">Other disclosure widget options</h3>
<p>To more clearly expose the boundaries of a disclosure widget to users of assistive technologies, the content container can be provided additional ARIA attributes which AT may use to convey ‘start’ and ‘end’ boundaries of the content.</p>
<p>For instance,</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><button</span>
<span class="na">type=</span><span class="s">button</span>
<span class="na">aria-expanded=</span><span class="s">...</span>
<span class="na">id=</span><span class="s">"dwtrigger"</span>
<span class="nt">></span>
Example name
<span class="nt"></button></span>
<span class="nt"><div</span> <span class="na">class=</span><span class="s">dw-panel</span>
<span class="na">role=</span><span class="s">"choose your role"</span>
<span class="na">aria-labelledby=</span><span class="s">"dwtrigger"</span><span class="nt">></span>
<span class="c"><!-- content goes here! --></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>A <code class="language-plaintext highlighter-rouge">role</code> attribute can be used to expose the content container as a particular type of <a href="https://w3c.github.io/aria//#document_structure_roles">document structure</a> or <a href="https://w3c.github.io/aria/#landmark_roles">landmark</a> element.</p>
<p>The type of role you choose will be contingent on the importance and type of content the disclosure widget represents. For instance, you might use a <code class="language-plaintext highlighter-rouge">group</code>, <code class="language-plaintext highlighter-rouge">region</code>, <code class="language-plaintext highlighter-rouge">article</code>, or even a <code class="language-plaintext highlighter-rouge">note</code> role. For simplicity’s sake, let’s use the <code class="language-plaintext highlighter-rouge">region</code> role for this example. The <a href="https://w3c.github.io/aria/#region"><code class="language-plaintext highlighter-rouge">region</code> role</a> representing a generic landmark element, so the assumption here is that this exposed content is important to call out and allow someone to quickly navigate back to, via screen reader landmark navigation commands, as necessary.</p>
<p>The <code class="language-plaintext highlighter-rouge">region</code> can be named by use of <code class="language-plaintext highlighter-rouge">aria-labelledby</code>, where the value of the attribute would be the <code class="language-plaintext highlighter-rouge">id</code> of the <code class="language-plaintext highlighter-rouge"><button></code> element.</p>
<p>A user expanding this disclosure widget and then entering the expanded contents could then be met with a “start of example name, region”. And when they are leaving, they might hear something along the lines of “end of example name, region”.</p>
<h2 id="ok-i-think-im-done-for-now">OK, I think I’m done for now</h2>
<p>So, this probably came across as me being down on <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code> which is unfortunate because I actually rather like the elements and outside of some current bugs - content model aside - they’re just so convenient to use.</p>
<p>For instance, here’s a very basic example of using them to create form group sections that can expand/collapse.</p>
<p class="codepen" data-height="601.6000061035156" data-default-tab="html,result" data-slug-hash="vYZyyqv" data-user="scottohara" style="height: 601.6000061035156px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/vYZyyqv">
expand/collapse form groups</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<p>With that said, I don’t have a neat little bow I can tie this whole thing up in. <code class="language-plaintext highlighter-rouge"><details></code> and <code class="language-plaintext highlighter-rouge"><summary></code> elements are complicated. Saying don’t use them isn’t really practical and there’s a lot of good things about them. But they do have bugs. I filed some. It’d be great if other people would file some too!</p>
<p>It really just all comes down to what sort of experience you want to provide to people, and how OK you might be knowing that the native elements are going to be exposed differently, have different gaps but also different features to them. Or, you can roll your own disclosure widgets and provide a consistent experience that you are fully in charge of… but it may not be as feature rich as what the browsers could provide, and your disclosure widgets need to degrade gracefully if viewed in a browser’s reading mode.</p>
<p>The decision of what you do is up to you. At least now, hopefully you have enough information to help you make it.</p>Scott O'HaraI've already written about the <details> and <summary> elements, but there is more to tell, and there have been some changes since I last wrote at length about them in 2018.Making a custom clear text field button2022-02-19T00:00:00+00:002022-02-19T00:00:00+00:00https://scottohara.me/blog/2022/02/19/custom-clear-buttons<p>When people add custom clear buttons to their text fields they generally do one of three things:</p>
<ol>
<li>They do not make them accessible at all.</li>
<li>They unnecessarily add them to the keyboard focus order of the web page.</li>
<li>They ensure the clear buttons are accessible - but not keyboard focusable. The clear button(s) do not show up when their associated fields are readonly or disabled, The button’s respect reading directionality of a web page (e.g., <code class="language-plaintext highlighter-rouge">dir=rtl</code>), and that keyboard focus returns to the text field once the clear button has been activated.</li>
</ol>
<p>I cheated a bit with that third list item… there’s quite a few things in there, and even a few others that I didn’t directly call out.</p>
<p>Today <a href="https://github.com/scottaohara/clear-text-field-button">I made a script</a> to demonstrate how to add custom clear buttons to text fields.</p>
<p>Here is a <a href="https://scottaohara.github.io/clear-text-field-button/">demo page and explainer</a> for more information on the markup pattern, script, and a more nuances that my piddly list did not cover.</p>
<p>Or you can check it out in the following iframe:</p>
<iframe title="clear text field button demo page" src="https://scottaohara.github.io/clear-text-field-button/" style="min-height: 35em; width: 100%;"></iframe>
<p>oh no! now there are two <code class="language-plaintext highlighter-rouge"><main></code> and <code class="language-plaintext highlighter-rouge"><h1></code> elements on my page because of the iframe content! Ah jeezy creezy… how will we all survive?</p>Scott O'HaraWhen people add custom clear buttons to their text fields they generally do one of three things: They do not make them accessible at all. They unnecessarily add them to the keyboard focus order of the web page. They ensure the clear buttons are accessible - but not keyboard focusable. The clear button(s) do not show up when their associated fields are readonly or disabled, The button’s respect reading directionality of a web page (e.g., dir=rtl), and that keyboard focus returns to the text field once the clear button has been activated.Are we live?2022-02-05T00:00:00+00:002022-02-05T00:00:00+00:00https://scottohara.me/blog/2022/02/05/are-we-live<p>If you have an interface where content is dynamically updated, and when the content is updated it does not receive focus, then you likely are going to need a live region.</p>
<p>Live regions are elements that inform assistive technologies, such as screen readers, that their descendant content may update. And, in such situations those updates need to be relayed to their users.</p>
<p>Live regions may be implemented into a web page via three different methods:</p>
<ul>
<li>Using specific <a href="https://www.w3.org/TR/wai-aria-1.2/#live_region_roles">ARIA live region roles</a>, such as <code class="language-plaintext highlighter-rouge">alert</code>, <code class="language-plaintext highlighter-rouge">log</code> or <code class="language-plaintext highlighter-rouge">status</code>. There’s also <code class="language-plaintext highlighter-rouge">marquee</code> and <code class="language-plaintext highlighter-rouge">timer</code>… but more on those in a second.</li>
<li>Using the <a href="https://www.w3.org/TR/wai-aria-1.2/#aria-live"><code class="language-plaintext highlighter-rouge">aria-live</code> attribute</a> to turn “any element” into a live region. There are three states that a live region may be in.
<ul>
<li><code class="language-plaintext highlighter-rouge">assertive</code>: high importance updates which take precedence over other announcements.</li>
<li><code class="language-plaintext highlighter-rouge">polite</code>: announcements that will be made at the next opportunity.</li>
<li><code class="language-plaintext highlighter-rouge">off</code>: announcements will not be made unless focus is presently on the live region element.</li>
</ul>
</li>
<li><a href="https://www.scottohara.me/blog/2019/07/10/the-output-element.html">Using HTML’s native <code class="language-plaintext highlighter-rouge"><output></code> element</a>. This element has the implicit ARIA role of <code class="language-plaintext highlighter-rouge">status</code>.</li>
</ul>
<p>Why you would use one method over the other will depend on how you want the content exposed to your users.</p>
<p>For instance, you might use one of the ARIA live region roles, or the <a href="https://html.spec.whatwg.org/multipage/form-elements.html#the-output-element"><code class="language-plaintext highlighter-rouge"><output></code> element</a>, if you intend to both have a section of content that will announce its updates to users, and this section of content would benefit from having its <a href="https://www.w3.org/TR/wai-aria-1.2/#live_region_roles">own unique role exposed</a>.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">role=</span><span class="s">alert</span><span class="nt">></span>
I likely have some sort of design that indicates I'm an alert.
You know. Like some red, or an exclamation point or something.
"Pay attention to me" kinda traits.
<span class="nt"></div></span>
</code></pre></div></div>
<p>You would use an <code class="language-plaintext highlighter-rouge">aria-live</code> attribute if you had an element, for instance a <code class="language-plaintext highlighter-rouge"><li></code>, <code class="language-plaintext highlighter-rouge"><p></code>, or a <code class="language-plaintext highlighter-rouge"><div></code>, where the element’s contents can dynamically update, but you either need to maintain the implicit role of the element, or there is no benefit in exposing an explicit role.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><ul</span> <span class="na">aria-live=</span><span class="s">polite</span> <span class="na">aria-atomic=</span><span class="s">false</span><span class="nt">></span>
<span class="nt"><li></span>My list semantics are important.<span class="nt"></li></span>
<span class="nt"><li></span>But I want you to know when new list items are added.<span class="nt"></li></span>
<span class="nt"></ul></span>
</code></pre></div></div>
<p>If you had an element that dynamically updated, but you didn’t want to constantly barrage users with announcements, then you might need <code class="language-plaintext highlighter-rouge">timer</code>, <code class="language-plaintext highlighter-rouge">marquee</code>, or a generic element with an <code class="language-plaintext highlighter-rouge">aria-live=off</code>.</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">role=</span><span class="s">marquee</span><span class="nt">></span>
I'm literally never going to stop talking... so best to ignore me by default.
<span class="nt"></div></span>
</code></pre></div></div>
<h2 id="live-regions-are-weird-and-finicky">Live regions are weird and finicky</h2>
<p>While everything I’ve mentioned so far is technically accurate, and the specs for live regions have received very few updates for some time, browsers and assistive technologies are quirky in how they expose live regions. Additionally, explicit instructions on how to implement live regions are rather sparse. There is an <a href="https://www.w3.org/TR/wai-aria-practices-1.2/#alert">Alert example</a> provided by the ARIA Authoring Practices. However, if you’ve spent time testing live regions you might already be aware that there are some unspoken best practices to help make sure live regions consistently (or as consistently as presently possible) work in your web interface.</p>
<h3 id="non-normative-rule-for-common-live-region-use-case">Non-normative rule for common live region use case</h3>
<p>One of the most common use cases for live regions is to announce a notification of some sort. This may range from a notification that visually appears on screen and is available to all users. Or, just as commonly, a live region might be used to indicate that content has updated somehow, but there is no explicit message as the information is visually apparent (e.g., it has started or finished loading). Such a scenario would generally require a visually hidden live region to inform people using screen readers of this state communicated via other visual means.</p>
<p>Many developers, reasonably, have tried to make these notifications clear by injecting a live region containing the necessary text on an as-needed basis. However, if testing beyond macOS VoiceOver, one would find that this is consistently the least supported way to ensure a live region is properly announced by screen readers.</p>
<div role="note">
<p>Dynamically injecting a live region node or changing an existing live region from <code>display: none</code> to <code>display: block</code> are effectively the same. In both cases (being absent or set to <code>display: none</code>) the live region does not exist in the a11y tree.</p>
</div>
<p>Rather, as the following test cases show, ensuring an empty live region exists in your DOM, and injecting content into it when necessary, proves to be the most robust way to ensure a live region will be announced.</p>
<p>View the <a href="https://codepen.io/scottohara/full/dyzxwyr" target="_blank">live region examples in a new window</a>.</p>
<p class="codepen" data-height="599" data-default-tab="html,result" data-slug-hash="dyzxwyr" data-user="scottohara" style="height: 599px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/dyzxwyr">
live region example</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<p>I would suggest further testing and re-testing as you implement live regions into your own projects. Results can vary as browsers and AT update. Having done a barrage of testing back in 2019, I’ve found that behaviors have changed enough (slightly for the better), that you should constantly be reviewing your live regions to ensure they are continuing to work as necessary.</p>
<p>As a general rule, if you can create an interface that can limit the number of live regions necessary - none being the ideal - then that’d be for the better.</p>
<p>However, please do keep in mind that something just as bad as live regions being injected into a web page and then making no announcements, is a web page with a bunch of live regions that all start barking at assistive technology users at the same time.</p>
<h2 id="more-information-on-live-regions">More information on live regions</h2>
<p>The ARIA working group is presently planning to take a hard review and look to make serious updates to live regions when work begins on ARIA 1.4. At the time of writing this, ARIA 1.2 has not yet reached W3C rec, but work on ARIA 1.3 has begun.</p>
<p>Additionally, there is this <a href="https://wicg.github.io/aom/notification-api.html">Notification API for Confirmation of Action</a> explainer which would be quite welcome as a way to better standardize live region behaviors.</p>
<p>There many more things to be said about live regions, their use cases and other best practices when using them. I’m not going to write about those though, because <a href="https://twitter.com/codingchaos">Sarah Higley</a> has already delivered an A11yTalks (July 2020) presentation to tell you all about them.</p>
<iframe width="560" height="315" src="https://www.youtube.com/embed/W5YAaLLBKhQ" title="A11yTalks YouTube video player" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture" allowfullscreen=""></iframe>
<h3 id="some-links">Some links</h3>
<ul>
<li><a href="https://www.scottohara.me/blog/2019/07/10/the-output-element.html">output: HTML’s native live region element </a></li>
<li><a href="https://www.w3.org/TR/wai-aria-1.2/#aria-atomic">ARIA 1.2: <code class="language-plaintext highlighter-rouge">aria-atomic</code></a></li>
<li><a href="https://www.w3.org/TR/wai-aria-1.2/#aria-relevant">ARIA 1.2: <code class="language-plaintext highlighter-rouge">aria-relevant</code></a></li>
<li><a href="https://www.w3.org/TR/wai-aria-1.2/#live_region_roles">ARIA 1.2: live region roles</a></li>
<li><a href="https://github.com/w3c/aria/issues?q=is%3Aissue+is%3Aopen+project%3Aw3c%2Faria%2F13+">ARIA Live Region Issues</a></li>
<li><a href="https://wicg.github.io/aom/notification-api.html">Notification API for Confirmation of Action</a></li>
</ul>Scott O'HaraIf you have an interface where content is dynamically updated, and when the content is updated it does not receive focus, then you likely are going to need a live region.Considering dynamic search results and content2022-02-05T00:00:00+00:002022-02-05T00:00:00+00:00https://scottohara.me/blog/2022/02/05/dynamic-results<p>Before we even begin, here are some key things I want you to take away from this post, which I had intended to write as a quick note of sorts… but I clearly don’t know how to to keep myself from exploding words everywhere.</p>
<details>
<summary>Some quick takeaways</summary>
<p>
Concerning UI where content is dynamically displayed or filtered based on user input from a text field:</p>
<ul>
<li>This is a proposal of behavior. You might need modifications for your specific use case. Get user feedback.</li>
<li>Provide people an instructional cue (accessible description) that "results will [adjective] as you type" for search components that dynamically render results. Use the words like "filter", "display", both or similar depending on the functionality of your component.</li>
<li>Live regions only communicate strings of text. If you need to convey information that has important semantics or other accessibility information, a live region is likely the wrong way to do it.</li>
<li>Constantly interrupting a user as they type with a live region should be avoided. Thus constantly announcing how many results are shown is not the best UX.</li>
<li>Some people might need more time to enter a search query due cognitive or motor disabilities. Some people might quickly type what they want and then navigate away from the text field before any dynamic announcement is made. Again, another reason to not rely heavily on live regions.</li>
<li>Do ensure that an assertive live region is used to immediately inform a user that no results were found. This is the one time we <strong>DO</strong> want to interrupt people, so they don't waste time typing when their query has already failed.</li>
<li>A live region will have the most robust support if it is an empty live region that is populated with content, rather than the live region itself being injected when rendering a message.</li>
<li>Consider other UX improvements that will help people quickly navigate your UI, which feel natural but also won't disrupt expected functionality for other controls.</li>
</ul>
</details>
<p>It has become common place for people to be presented with web interfaces where one can type into a search or filter text field, and a script can run to (almost) immediately return relevant results on the same page.</p>
<figure aria-label="mockup of a search ui" style="padding: 1em; border: 1px solid;">
<img src="https://www.scottohara.me/assets/img/articles/isitme.jpg" alt="a mocked up search results page, where the queries and results text are all parodying Lionel Richie's song 'hello'" style="margin:0" />
<figcaption style="margin: 1em 0 0; padding: 0;">
<p style="font-size: .75em;">A representation of what a dynamic search/filter UI might look like... where it also is parodying lyrics of Lionel Richie's song 'hello' for no particular reason. I did not just lift this design directly from <a href="https://a11ysupport.io">a11ysupport.io</a> and modify the text of the page using browser dev tools. I am also lying because I did exactly that.</p>
</figcaption>
</figure>
<p>Such an interface is useful and provides an immediacy to slower experiences of days past. Rather than require people input their query, hit submit, wait for the new page or screen of results to return and <strong>then</strong> they can determine if the results contain what they want or not… dynamically display the results, or filtering down already displayed content cuts out all the waiting.</p>
<div role="note">
<p>Please note, the described UI is not referring to <a href="https://www.w3.org/TR/wai-aria-practices-1.2/examples/combobox/combobox-autocomplete-both.html">autocomplete text fields / comboboxes</a>.</p>
<details>
<summary>more info and spoilers</summary>
<p>
This post is specifically going over a search or filtering UI where people can type into a text field and results immediately display, filter, or both on the current web page. You can see an example of content dynamically filtering at <a href="https://a11ysupport.io">a11ysupport.io</a>.
</p>
<p>
<strong>Spoilers:</strong> <a href="https://a11ysupport.io">a11ysupport.io</a> employs a lot of the accessibility considerations this post outlines. Thanks <a href="https://twitter.com/mfairchild365">@mfairchild365</a>.
</p>
</details>
</div>
<p>Of course, this then begs the question of how do you convey such information to people who use assistive technologies. Such as screen readers. If someone cannot see the page, how will they know when results are dynamically returned, let alone that results are going to be rendered at all?</p>
<p>If you are familiar with web accessibility, and in particular <a href="https://www.w3.org/TR/wai-aria-1.2/">ARIA</a>, you might be thinking “use a <a href="https://www.w3.org/TR/wai-aria-1.2/#dfn-live-region">live region</a>!”</p>
<p>If you are familiar with live regions, such a blanket recommendation might make you think “for the love of everything you hold dear, <a href="https://www.scottohara.me/blog/2022/02/05/are-we-live.html">please do not use a live region!</a>”</p>
<h2 id="one-thing-at-a-time">One thing at a time</h2>
<p>Let’s back up a second and level set on the situation and come to a few base understandings.</p>
<h3 id="first-live-regions">First, live regions</h3>
<p>Live regions are used when you need to communicate information that is being presented visually, but someone using assistive technologies (AT) might not be aware of. The situations where this would occur are for people with low or no vision, where they are specifically focused or scanning or interacting with one part of a web page, but the dynamic update is occurring somewhere else. Similarly, people with low vision who might be looking at a zoomed in, or magnified portion of their screen, but the updated visual occurs outside of their current area of visibility.</p>
<p>A live region can be used to communicate this information to these people if they are using AT, such as screen readers, which can ‘listen’ for dynamic changes that are exposed via live regions. However, even with live regions there are limitations to what will be communicated.</p>
<p>For instance, live regions are a good tool for communicating a simple string of content. E.g., “your information has been saved!” or “there was a problem with X. Please try again.”</p>
<p>Live regions are rubbish if you need to communicate large swaths of information, or information that would best be communicated by other semantics and accessibility properties. This is because live regions <strong>only</strong> communicate strings of text.</p>
<p>For example:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><div</span> <span class="na">aria-live=</span><span class="s">"polite"</span><span class="nt">></span>
<span class="nt"><button></span>You'll have to guess what I represent!<span class="nt"></button></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>The text of the <code class="language-plaintext highlighter-rouge"><button></code> would be announced when it was injected into the live region. The fact that the text represents the label of a button would not be communicated… as there would be no mention of the button’s role in the live region announcement.</p>
<p>Now, the reason I mention all of that is because a common misunderstanding about how to communicate the returned results for dynamic filtering of content or search results is to wrap all of said content in a live region.</p>
<p>No. Gross. Never.</p>
<p>Consider the following:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nt"><label></span>
Search help articles
<span class="nt"><input</span> <span class="na">type=</span><span class="s">text</span><span class="nt">></span>
<span class="nt"></label></span>
<span class="nt"><h2></span>Results:<span class="nt"></h2></span>
<span class="nt"><div</span> <span class="na">aria-live=</span><span class="s">"polite"</span><span class="nt">></span>
<span class="nt"><ul></span>
<span class="nt"><li></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"..."</span><span class="nt">></span>Title of article<span class="nt"></a></span>
<span class="nt"><p></span>maybe some description goes here<span class="nt"></p></span>
<span class="nt"></li></span>
<span class="nt"><li></span>
<span class="nt"><a</span> <span class="na">href=</span><span class="s">"..."</span><span class="nt">></span>Title of article<span class="nt"></a></span>
<span class="nt"><p></span>maybe some description goes here<span class="nt"></p></span>
<span class="nt"></li></span>
...
<span class="nt"></ul></span>
<span class="nt"></div></span>
</code></pre></div></div>
<p>If the above results were injected into a live region, someone using a screen reader would hear a flattened text string of the content as a single uninterrupted announcement. Assuming the native bullet list markers have not been removed from the results list, the announcement could resemble the following:</p>
<blockquote>
<p>“bullet, Title of article maybe some description goes here. bullet, Title of article maybe some description goes here …”</p>
</blockquote>
<p>So that’s awful.</p>
<p>And because I like making silly comparisons to explain information, it’s awful because it would be like someone asking you how many pages are in a book. Then, rather than give a concise answer, you instead shout at them the entirety of the book’s text. You do this until they literally tell you to shut up (<kbd>Ctrl</kbd> key), or until you run out of air and collapse.</p>
<p>OK, point taken. So instead we should just let people know that content has been updated, or let them know the specific number of results that are being dynamically displayed. Right?</p>
<p>Hmm… Maybe? Sorta? Opinions will vary, and there are still live region gotchas to consider. Read on…</p>
<h3 id="second-im-typing-dont-interrupt-me-well-unless">Second, I’m typing. Don’t interrupt me… well, unless…</h3>
<p>Another thing to keep in mind about this particular use case, is that someone is actively trying to enter their query into a text field. Imagine trying to type a word, but after every character you type the UI intrusively displays a message that covers the screen saying “results are available” or “X results are available”.</p>
<p>That might be helpful. <em>Once</em>. Particularly if you weren’t already aware that this is how the component behaves. But, doing that after every single character entry gets annoying real fast.</p>
<p>We don’t want to constantly interrupt and distract people as they type. Additionally, unless there is a number of returned results displayed in text – it’s rather pointless to convey how many results are being rendered. This is especially true when the results may be so great that the total number would be unknown, even if one could see the whole screen at once.</p>
<p>Now, there can be value in letting people know when they have filtered results down to just a few instances. But even if waiting until only a few results are shown, interrupting announcements are <em>still interrupting announcements</em>.</p>
<p>Who’s to say what number is the number where it becomes OK to start such behavior? Some people might want 10? Maybe five? What if all the results are very similar even at five, and the best way to make sure the right result is returned is to just finish the phrase? This is where opinions will vary and complicate things. Regardless of what you decide, someone will not be happy.</p>
<p>With that in mind, a solution to frequent interrupting announcements <em>could</em> be to only announce after a delay of inactivity. For instance, a live region could remain empty until a long enough delay since the last key press has been detected. But what is “long enough” will depend on the person. Some people might need more time to type due to cognitive or motor disabilities. Some people might quickly type what they want and then navigate away from the text field before an announcement can even be made. Sounds like opinion territory again, no?</p>
<p>Another thing to consider: while we don’t want to constantly interrupt people while typing, we <strong>need</strong> to interrupt when things go afoul. Specifically, let someone know immediately when they have entered a query that returns no results.</p>
<p>Delaying such an announcement would result in wasted time, and potential uncertainty about <em>“when”</em> someone’s query stopped working. Was it because of a typo? Was it because of an added word that made their query too specific? People who can see the UI are likely to notice right away when the dynamic content dries up. They can then immediately correct for this by adjusting their query. This same affordance <strong>must</strong> be provided to people with disabilities.</p>
<h2 id="a-proposal">A proposal</h2>
<p>People want and need different things. So, rather than try to come up with the perfect live region solution and determine the amount of time necessary to delay such dynamic announcements (which will never please everyone), consider the following proposal:</p>
<div class="language-html highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="c"><!--
the text field to perform the search/filter.
--></span>
<span class="nt"><label></span>
[ accessible name/label for field ]
<span class="nt"><input</span> <span class="na">id=</span><span class="s">field</span> <span class="na">aria-describedby=</span><span class="s">info</span><span class="nt">></span>
<span class="nt"></label></span>
<span class="c"><!--
other stuff goes here. or maybe nothing.
it's your UI after all...
--></span>
<span class="c"><!--
a heading to introduce the results/content.
use the appropriate level.
--></span>
<span class="nt"><h</span><span class="na">#</span> <span class="na">tabindex=</span><span class="s">-1</span><span class="nt">></span>
<span class="c"><!-- optionally use the query in the heading --></span>
Results for [query]
<span class="c"><!--
it can be useful to include the number of results in the heading.
style as needed with CSS.
--></span>
<span class="nt"><span</span> <span class="na">hidden</span><span class="nt">></span># available<span class="nt"></span></span>
<span class="nt"></h</span><span class="err">#</span><span class="nt">></span>
<span class="c"><!--
text to indicate how this UI works.
it must be associated with the text field.
--></span>
<span class="nt"><div</span> <span class="na">id=</span><span class="s">info</span><span class="nt">></span>
<span class="c"><!--
may be visual or hidden depending on persnickety individuals.
this text could persistently go near the text field as well.
--></span>
Results will update as you type.
<span class="nt"></div></span>
<span class="c"><!--
empty, unstyled live region just waiting for no results
to be displayed.
--></span>
<span class="nt"><div</span> <span class="na">aria-live=</span><span class="s">assertive</span> <span class="na">aria-atomic=</span><span class="s">true</span><span class="nt">></span>
<span class="c"><!-- if no result found, inject that message here --></span>
<span class="nt"></div></span>
<span class="c"><!--
markup for rendering search results goes here.
it's whatever you need it to be.
--></span>
</code></pre></div></div>
<h3 id="the-text-field">The text field</h3>
<p>Use a <a href="https://adrianroselli.com/2019/07/ignore-typesearch.html">text field or search field</a> with an appropriate visible label.</p>
<p>Provide that field with a description to indicate that “results will update as you type” (or some similar verbage that your content team can decide upon. People are so picky with their words…).</p>
<p>In the markup snippet above, <code class="language-plaintext highlighter-rouge">aria-describedby</code> is used to associate the description of behavior with the text field. Ideally this description is visible so it can inform <em>everyone</em> of what to expect. However, pick your battles if necessary. If this cannot be visible because people value their aesthetic opinions over usability, then just make sure the description is programmatically associated with the form field.</p>
<p>When someone navigates to the text field, the description can be conveyed to their assistive technology. This will inform this person of the behavior they can expect. Telling someone this up front sets expectations, allowing content to update without needing to constantly inform them that such content will update.</p>
<p><strong>Arguably</strong>, one could make a case for appending this behavior to the end of the field’s accessible name. A valid reason being some people turn off the automatic announcement of element descriptions. I’m personally of two minds on this, as I do think it’s important to inform people of the behavior… but such information can become unnecessarily redundant the more someone is used to interacting with a particular UI.</p>
<p>For instance, how many of us really benefit from being informed what seat belts are and how they work every time we travel by airplane? Adding instructions to the accessible names of controls is like <em>forcing</em> people to listen to that. I prefer the route of providing the necessary information, but allowing the individual to easily ignore if they so choose.</p>
<h3 id="the-results--dynamic-content-section">The results / dynamic content section</h3>
<p>Moving on… introducing the search results with a heading will allow people a consistent location they can expect results to populate in. This benefits everyone in the end. But people who use screen readers generally get a lay of the land (or the DOM… whatever) when they come to new web pages. Much like someone with full vision will quickly glance around a web page to get at least a basic understanding of the information presented.</p>
<p>I don’t care to be prescriptive in the manner in which you markup your search results. Whether they be a list, or a series of headings containing links with a descriptive sentence for more context… both can be valid, so have at it.</p>
<h3 id="communicating-no-results">Communicating no results</h3>
<p>While we do not want to use live regions to constantly interrupt people as they type, there <strong>is benefit</strong> to interrupting someone when no results are found. If a message is displayed to indicate to indicate a no results state, then it actually becomes a <a href="https://www.w3.org/WAI/WCAG21/Understanding/status-messages.html">WCAG 2.1 requirement that the status message is announced</a>.</p>
<p>The live region is an empty <code class="language-plaintext highlighter-rouge"><div aria-live=assertive></code>. I suggest having a generic live region rather than using a specific <a href="https://www.w3.org/TR/wai-aria-1.2/#live_region_roles">live region <code class="language-plaintext highlighter-rouge">role</code></a>.</p>
<details>
<summary>Context for suggesting a generic live region</summary>
<p>I personally don't see much of a reason to use a role for such a message, but I specifically don't want to use a <code>role=alert</code>.</p>
<p>While an <code>alert</code> would create an assertive live region, some screen readers and browsers will immediately announce the existence of an "alert" on page render (I'm specifically looking at you, TalkBack).</p>
<p>Essentially, an "alert" announcement will be made immediately (the element's role). But as it has no content yet... because nothing's gone wrong at this point, there's no indication as to why this "alert" was announced.</p>
<p>Granted, such behavior should be considered a bug... but I don't want to deal with that nonsense. There's too many other things to worry about, so I suggest we just avoid in this instance. Do file a bug if you like, though :)</p>
</details>
<p>Due to live regions being notoriously awful at being announced when injected into a web page, ensure an <a href="https://www.scottohara.me/blog/2022/02/05/are-we-live.html">empty live region that content can be injected into</a> exists in the DOM. Using live regions in this manner offers the most robust support to ensure their announcement will be made by as many screen reader and browser combinations as possible.</p>
<h3 id="wait-what-happens-if-i-press-enter">Wait. What happens if I press <kbd>Enter</kbd>?</h3>
<p>Ah, good question. That’ll depend on the type of search component you are creating. Essentially, there are two types of dynamic search or filtering components.</p>
<p>First, there may be instances of components like these that return “quick results”. Someone entering their query might get common suggested results, but if they hit enter, or activate a “submit button”, the query will be sent and a full results page will be rendered. So in that case, <kbd>Enter</kbd> essentially works as generally expected. It submits the search query.</p>
<p>For the second type,results are only shown and filtered on the current page. There is no formal submission of a query. But muscle memory is weird… and unless you explicitly mention that there is no form submission, people might try it out anyway.</p>
<p>Most will then realize, when a whole bunch of nothing happens, that what they get is what they get. They’ll then move on and start interacting with the content of the page.</p>
<p>But, we could go beyond the bare minimum of making a WCAG-conforming user experience and provide some extra functionality for people who might want or expect <kbd>Enter</kbd> to do something?</p>
<p>Just because WCAG says you only have to give someone a turkey sandwich, that doesn’t mean you can’t give them condiments to go with it.</p>
<p>By listening for an <kbd>Enter</kbd> key press, JavaScript can programmatically move focus to the heading that introduces the result listing. If placing the number of found results within the heading, then that information will be announced along with the static heading text.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="kd">const</span> <span class="nx">field</span> <span class="o">=</span> <span class="nb">document</span><span class="p">.</span><span class="nx">getElementById</span><span class="p">(</span><span class="dl">'</span><span class="s1">field</span><span class="dl">'</span><span class="p">);</span>
<span class="nx">field</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">keyup</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span> <span class="nx">e</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">==</span> <span class="dl">'</span><span class="s1">Enter</span><span class="dl">'</span> <span class="p">)</span> <span class="p">{</span>
<span class="nx">heading</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>While we’re at it, why not provide a quick mechanism to return to the search field as well? In discussing this pattern with one of my coworkers, he found himself wanting to press the <kbd>Esc</kbd> key to return to the search field. Even though he was aware that was unexpected functionality for such a UI, it just seemed to fit how he’d expect this to work.</p>
<p>Now, one person’s expectation doesn’t make for a standard, but it was a good idea. More importantly, implementing this functionality generally won’t disrupt anyone’s experience, and it’ll be useful for those who had similar expectations.</p>
<div class="language-js highlighter-rouge"><div class="highlight"><pre class="highlight"><code><span class="nx">heading</span><span class="p">.</span><span class="nx">addEventListener</span><span class="p">(</span><span class="dl">'</span><span class="s1">keyup</span><span class="dl">'</span><span class="p">,</span> <span class="kd">function</span> <span class="p">(</span> <span class="nx">e</span> <span class="p">)</span> <span class="p">{</span>
<span class="k">if</span> <span class="p">(</span> <span class="nx">e</span><span class="p">.</span><span class="nx">key</span> <span class="o">==</span> <span class="dl">"</span><span class="s2">Escape</span><span class="dl">"</span> <span class="p">)</span> <span class="p">{</span>
<span class="nx">field</span><span class="p">.</span><span class="nx">focus</span><span class="p">();</span>
<span class="p">}</span>
<span class="p">});</span>
</code></pre></div></div>
<p>The one caveat to this is to make sure that if you have this UI within a dialog, or some other form of “popup” that will close if pressing the <kbd>Esc</kbd> key, that you make sure to suppress this functionality when the heading is focused.</p>
<h2 id="the-outcome">The outcome</h2>
<p>And with that, the following represents a quick demo of expected behavior. The same results will return no matter what you enter into the text field, and a no results found message will appear as soon as more than 5 characters are detected in the text field.</p>
<p class="codepen" data-height="300" data-default-tab="html,result" data-slug-hash="zYPByjo" data-user="scottohara" style="height: 300px; box-sizing: border-box; display: flex; align-items: center; justify-content: center; border: 2px solid; margin: 1em 0; padding: 1em;">
<span>See the Pen <a href="https://codepen.io/scottohara/pen/zYPByjo">
quick demo of showing / informing about dynamic results</a> by Scott (<a href="https://codepen.io/scottohara">@scottohara</a>)
on <a href="https://codepen.io">CodePen</a>.</span>
</p>
<script async="" src="https://cpwebassets.codepen.io/assets/embed/ei.js"></script>
<p>You can also check out <a href="https://a11ysupport.io">a11ysupport.io</a> which has implemented ideas from this post into the search/filter field there. You know, if you want to check out a real example, rather than my sloppy demo trash :)</p>Scott O'HaraBefore we even begin, here are some key things I want you to take away from this post, which I had intended to write as a quick note of sorts… but I clearly don’t know how to to keep myself from exploding words everywhere.