Adrian Roselli AI-Powered Blockchain Crypto Framework IPA Tue, 26 May 2020 15:32:33 +0000 en-US hourly 1 Disclosure Widgets Wed, 20 May 2020 12:45:23 +0000

A disclosure widget is a simple control whose sole purpose is to hide or show stuff.


HTML has one built in via the <details> and <summary> elements. Until recently, if you wanted to use it in modern browsers you needed to use a polyfill. In most cases it was easier and simpler to skip the polyfill and use ARIA and JavaScript to make a disclosure widget.

Today you can use either method, and it is not hard to style them to look the same. If you need to support IE11, then you have your choice between the polyfill and an ARIA disclosure widget. I have more details on detecting support and using the polyfill in an old post.

For a simple disclosure pattern, where you have a summary that leads into details, then <details>/<summary> are a good fit. Once you start to go much beyond that, things get more complicated. As I wrote in Details / Summary Are Not [insert control here], it can be tempting to extend them to do more, but it may not work the way you want for all users.

ARIA Disclosure Widget

The structure is straightforward. A <button> (or a control with role="button") acts as the control that toggles display.

Add aria-expanded with either true or false to convey to assistive technology whether the widget is expanded or not.

Add aria-controls with with the value of the id from the element whose visibility is being toggled. This is optional, and support in assistive technology is sparse, but this programmatic association may allow for easier reference via CSS or script.

Keyboard Interaction

When the disclosure control has focus either Enter or Space activates the disclosure control and toggles the visibility of the disclosure content.

WAI-ARIA Roles, States, and Properties

  • The element that shows and hides the content has the button role.
  • When the content is visible, the element with role button has aria-expanded set to true. When the content area is hidden, it is set to false.
  • Optionally, the element with role button has a value specified for aria-controls that refers to the element that contains all the content that is shown or hidden by referencing its id.

Focus and Reading Order

For a simple disclosure do not move focus. This allows a user who accidentally triggers the control to not lose their place and immediately trigger it again.

Ensure the disclosed content immediately follows the control in the DOM. For screen reader users this allows them to continue reading without the need to hunt for the content. If the disclosed content includes interactive controls (tab stops), this allows keyboard users to quickly move to those controls.

ARIA Authoring Practices

The disclosure pattern at the WAI-ARIA Authoring Practices 1.1 is one of the few patterns there that is complete and works well across devices and platforms. The examples are also simple and straightforward.

Simple Disclosure

This example shows the basic implementation of keyboard support and ARIA. You can also see a debug version of the simple disclosure widget

See the Pen Basic Disclosure Widget by Adrian Roselli (@aardrian) on CodePen.

The following video shows how each part of the HTML is used by the screen reader to announce the name, role, and state of the control, while also showing the values and styles change.

The embedded example as seen using JAWS 2018 and Chrome 81. I used open captions (burned into the video) instead of closed captions (which allow you to adjust text) because this is a visual aid for sighted developers and I wanted to ensure the captions did not overlap the code.


This JavaScript is by no means ideal, nor is it progressively enhanced. It is only meant to show that you have to change one property only — aria-expanded.

function toggleDisclosure(btnID) {
  // Get the button that triggered this
  var theButton = document.getElementById(btnID);
  // If the button is not expanded...
  if (theButton.getAttribute("aria-expanded") == "false") {
    // Now set the button to expanded
    theButton.setAttribute("aria-expanded", "true");
  // Otherwise button is not expanded...
  } else {
    // Now set the button to collapsed
    theButton.setAttribute("aria-expanded", "false");


The CSS is even simpler. You want to either hide content or leave it be. What this means is you should toggle between display: none and allowing the default to apply. Since setting the wrong display property can remove its semantics for assistive technology, it is likely safest to not define the default state and let the native semantics of the element apply or, if using display properties for layout already, allowing those to apply.

button[aria-expanded="false"] + div {
  display: none;

The attribute selector ensures you are only visually toggling the display when the programmatic state has been correctly set.

The next-sibling combinator (+, formerly the adjacent sibling combinator) means that your disclosed content container must immediately follow the control in the DOM, helping to enforce the reading and focus order.

Extended Disclosure Patterns

Disclosure widgets are not restricted to just toggling narrative content. If you start to use them to mimic menu-like controls then you may need to add support for Esc or click-outside-to-close. Similarly, support for arrow keys, Home, End, Page Up, Page Down and potentially Shift + F10 may be warranted. If displaying the disclosed content overlaying other content, then z-index will be necessary.

No matter what you initially add, testing with users will be critical to creating a pattern that makes sense. Remember that once you start to modify an existing vetted pattern, that makes it no longer an existing vetted pattern.

In Link + Disclosure Widget Navigation I show how disclosure widgets can be used to create site navigation. Since disclosure widgets can contain structured and interactive content, this can become a mega-menu easily.

Using Firefox 68.0b3 and NVDA 2019.1.1. You do not hear it announced, but when on the About button and the Press Events sub-link I press Esc to hide the list.

Table with Expando Rows

My post Table with Expando Rows demonstrates how disclosure widgets can toggle the display of structured content, such as table rows.

Using JAWS 2018 and Internet Explorer 11 to navigate the table with a disclosure widget that lives in a single cell. Note that JAWS is visually highlighting the first cell in a row that has content, not the blank cells. It also does not highlight the buttons.


An accordion is more than a few disclosure widgets or <details>/<summary> elements one after another. While an accordion is built up from a series of disclosure widgets, these widgets should be grouped and use a group label to inform screen reader users of the relationship.

Ideally they also should allow only one to be open at a time, and restrict the height of the entire collection to no taller than the viewport. The height is maintained by making the disclosed panels for each scroll instead. The accordion section of the article Designing for Progressive Disclosure gives more detail (along with other disclosure widget uses)

The following example demonstrates it, but the code is not production ready. You can also view the debug version of this accordion.

See the Pen Accordion by Adrian Roselli (@aardrian) on CodePen.

If you are interested in code that is more production-ready and progressively-enhanced, start with Scott O’Hara’s Progressively enhanced ARIA accordions. If you agree with me that the accordion should have a fixed height and scrolling panels within, then you will need to adapt Scott’s code.

Other Uses

Disclosure widgets can be modified a bit to take on other tasks. A simple example is a hamburger navigation. A more complex example could be a tool-tip replacement. An even more complex example is to create a listbox-like control that, unlike a listbox, allows the control to contain content other than items with the option role.

Hamburger-style navigation from a mobile layout. A control that looks like an expanded select box, but has plain text, two date fields, and a link. A question mark next to a piece of text, with a large box of text overlapping the content.
Here I show a hamburger navigation button, a listbox-like control, and a tool-tip-like box. I opted against full code samples since building them was delaying this post for way too long.

Each of the examples above warrants testing with real users — specifically the users of your product / service / screens. That testing should include people with disabilities, ideally whose skill levels correspond to your audience.

Closing the Widget

Each of these examples should close when the user presses Esc. This does not correspond to the native <details> / <summary> experience. However, there are many cases where closing (hiding) on Esc make sense, particularly where your control can obscure other content.

Some design implementations may warrant closing (hiding) the additional content when a user clicks or taps outside the content area, particularly when used as navigation or controls that resemble native form elements.

Managing Focus

Disclosure widgets by default do not move focus to the content area that is displayed. For your users, there may be cases where that makes sense.

In those scenarios determine if moving focus to the first interactive item is the best fit, or to the container instead. If you move focus to the container, the container must be able to accept focus (tabindex attribute) and have an accessible name and appropriate group role.

Keyboard Navigation

If you are using a visual style that looks like a <select>, then you may want to add support for arrow keys. Bear in mind, if you do that you will also want to add support for Home, End, Page Up, Page Down and potentially Shift + F10.

Remember that screen readers intercept arrow keys, so be certain to test. It is important you understand how it functions and if there are any problems for real users.

Displaying on Hover or Focus

Generally you want to avoid revealing the hidden content when a control receives focus or is hovered. Tool-tips are a valid exception, but in most other cases this will produce usability challenges.

If you pursue it, ensure that there is no nested interactive content, as a user may be unable to access it when only hovering. Unless the revealed content persists. Which means it will also need to be dismissable (see Esc support above). See Success Criterion 1.4.13: Content on Hover or Focus (AA, WCAG 2.1) for more detail.

Native versus ARIA Comparison

That was a lot. From here to the end I am just tracking screen reader announcement.

Visually the native <details>/<summary> can generally be styled to look the same as a simple disclosure. You may need different selectors since in an ARIA disclosure the control and the disclosed content are (or should be) siblings, while the <details> element is the parent to <summary>.

See the Pen Native vs ARIA Disclosure Widget by Adrian Roselli (@aardrian) on CodePen.

Following are examples of how different screen reader and browser combinations announce the two patterns. I use accName to stand in for the visible text on the controls so you do not confuse the name with what the screen reader announces.

JAWS 2019 / Chrome 81

As far as JAWS is concerned, these are the same control. Under the hood in Chrome, the <summary> has a role of DisclosureTriangle, but it is either mapped to the button role or just announced as if it is. Note that if you use a control other than a <button> for the ARIA pattern (why?), but use the button role, you had better add support for the Space bar since that is what JAWS tells users to press.

accName button [collapsed|expanded]. To activate press Space bar.

NVDA 2019 / Firefox 76

NVDA also considers them to be the same, at least for the user. Under the hood in Firefox, the <summary> has the summary role, and the <button> has the pushbutton role.

accName [collapsed|expanded].
accName [collapsed|expanded].

macOS 10.15.2 / VoiceOver / Safari

VoiceOver considers them functionally the same, but since each is a different control type it says so.

accName [collapsed|expanded] summary.
accName [collapsed|expanded] button.

Android 10 / Chrome 81 / TalkBack

TalkBack with Chrome will announce <details>/<summary> as disclosure triangle, while the ARIA pattern is announced as button (because I used a <button>).

[collapsed|expanded] accName disclosure triangle
[collapsed|expanded] accName button

Android 10 / Firefox 68 / TalkBack

TalkBack with Firefox will not announce <details>/<summary> when navigating by controls; it gets skipped completely. If <summary> has a child node (such as an SVG), it becomes another tab-stop. The state of neither is conveyed.

accName button. Double-tap to activate.

iPadOS 13.4 / VoiceOver / Safari

VoiceOver does not expose <summary> as interactive when the <details> is expanded. This may not be a big deal unless you have verbose content or controls that would annoy VoiceOver users to have to navigate through or otherwise jump past (if not, why did you use it?).

Native, collapsed
accName collapsed. Double-tap to expand.
Native, expanded
ARIA, collapsed
accName button collapsed. Double-tap to expand.
ARIA, expanded
accName button expanded. Double-tap to expand.


A disclosure widget is a simple control whose sole purpose is to hide or show stuff. Using the native control should be limited to simple content. The ARIA pattern can be extended to behave as richer controls. No matter what, always test.

]]> 1
Avoid aria-roledescription Thu, 30 Apr 2020 13:58:56 +0000 HTML has all sorts of built-in features that, when used correctly, are accessible, will localize, and which just work. For example, if I want a button, I use <button>, and a screen reader will announce it as button. For users in other languages, they will hear whatever is their word for button.

For cases where HTML does not have the control built in, ARIA can be used to fill that gap. If you want a tabbed interface, for example, you can use the tab, tablist, and tabpanel roles. A screen reader will announce those components for users in their own language through no additional effort on your part.

A New Property

ARIA 1.1 introduced a new property: aria-roledescription

Defines a human-readable, author-localized description for the role of an element.


The aria-roledescription property gives authors the ability to override how assistive technologies localize and express the name of a role. Thus inappropriately using aria-roledescription may inhibit users’ ability to understand or interact with an element. Authors SHOULD limit use of aria-roledescription to clarifying the purpose of non-interactive container roles like group or region, or to providing a more specific description of a widget.

When using aria-roledescription, authors SHOULD also ensure that:

  1. The element to which aria-roledescription is applied has a valid WAI-ARIA role or has an implicit WAI-ARIA role semantic.
  2. The value of aria-roledescription is not empty or does not contain only whitespace characters.

To summarize: this property allows an author to override what role is announced to a screen reader user. There is some guidance: no blank values, only use it on things that already have implicit or explicit roles, and use it only to clarify a thing.

Do Not Do This

There are reasons to be careful with aria-roledescription.


Every time I see aria-roledescription in the wild, it is to offer hint text. Something like this:

<label for="Foo">Phone</label>
<input type="text" id="Foo" aria-roledescription="Only numbers are allowed">

This developer has effectively told the screen reader that instead of announcing input type in text to replace it and say Only numbers are allowed.

There is no HTML element by that name. There is no ARIA widget by that name. It is meaningless and hides the control type. Is it a text input? A number input? A calendar widget? A range slider?

This happens enough with aria-roledescription that there is an open issue about using it for hint text in the ARIA spec.


The ARIA 1.1 spec is clear that the value from aria-roledescription overrides the native role. The spec offers an example of adding the original role into the aria-roledescription to provide interaction guidance to users:

<button aria-roledescription="attachment button">

This makes sense when you want to qualify the kind of a control in the context of a specific use. Support in browsers and screen readers suggests this is probably the safest approach.

But right now, a screen reader is just as likely to also announce the role as the aria-roledescription value. In JAWS on Chrome, for example, that button is announced as attachment button button.

Because of the misuse I outlined with hints, there is an open issue to make the extra announcement standard.


Today your native and ARIA controls are announced in whatever language the user speaks. If the user has their screen reader and browser in Portuguese, they will hear <button> announced as botão.

Once you override its role with aria-roledescription, they will hear it in whatever language you provided. There is no auto-translation. We already know you cannot rely on aria-label to auto translate, but it definitely will not with aria-roledescription.

That attachment button will sound like attachment button botão to our Portuguese JAWS user (and with a Portuguese pronunciation on all the words). Here is sort of an inverse example:

<button lang="pt" aria-roledescription="meu berimbau botão">

When you use aria-roledescription you are freezing that widget or region to one language. That means you have to fold this node into your localization workflow. To presume that your screen will only be used by, say, Americans who speak English is the height of hubris considering how many Americans only speak English as their second language.


One of my biggest fears is that someone somewhere decides that the way assorted screen readers announce the same control differently is enough of concern that they will want to standardize the end user experience to their own preferences or experience.

Do you find it weird that JAWS and NVDA call a <select> a combobox while VoiceOver and TalkBack call it a pop-up button? Does your team refer to everything that alternates between hidden and visible as a drop-down, regardless of how it functions? Have I got a trick for you!

<select aria-roledescription="dropdown">

This is not an irrational fear dreamt up from nowhere. I have met people in QA roles who cannot abide the different names for <select> when testing because it does not match the story or script or spec or whatever artificial document they have.

Maybe a Use Case?

If you have a very specific audience, that is homogeneous in language, in skill, in experience, and in technology, and that audience has struggled with a specific concept or control, then sure, maybe experiment.

The ARIA 1.1 spec gives a couple reasonable examples, such as a button to download an attachment or a slide in a carousel.

You could add it to a wrapper to make a set of disconnected widgets present as a contiguous whole. Though if you are only doing it to convey a visual concept that blind screen reader users care little about, skip it. Further, an interesting feature in JAWS is to ignore the aria-roledescription on the region, but override the nested controls instead (see the accordion example below).

Perhaps you create a completely novel interface using canvas for a specific set of users that you can train and prepare. In that highly specific scenario, sure? Give it a shot and test it with your users?

Maybe you find a button with aria-pressed or a checkbox is not sufficiently clear to users as a toggle. In that case it might be worth experimenting with it, making sure that if it matches a platform native control that your co-opted version behaves in exactly the same way:

<button aria-pressed="false" aria-roledescription="toggle">
<button aria-pressed="false" aria-roledescription="switch">

Either way, if you only ever approach aria-roledescription as an option worth testing to remedy a documented problem, then you may be in good shape.


I made a pen with the above examples that you can try with your preferred screen reader and browser combination. When I have time later I will record some of the output and add it. In the meantime, visit the pen in debug mode or play with it below.

See the Pen QWjMPgV by Adrian Roselli (@aardrian) on CodePen.


Running Firefox 76.0 and NVDA 2019.3.1 on Windows 10. You can hear each control with its aria-roledescription overriding the native control name, including the link (which I do not reference above). Pay attention to how it announces the accordion region. You also can tell I do not have a Portuguese language pack installed.
Running Chrome 81.0.4044.129 and JAWS 2018.1808.10 on Windows 10. The missing spaces in the captions are intentional. The accessible name and aria-roledescription values are sometimes concatenated. You can hear this most clearly on the <select> where JAWS inserts an extra syllable between the two words. You can also hear the link is not changed (but is mis-announced because of this accName bug in Chrome), and all the buttons in the accordion have their role over-written even though that is not what the code is doing.
Running Internet Explorer 11 and JAWS 2018.1808.10 on Windows 10. The missing spaces are intentional. You can hear JAWS applies aria-roledescription to the <button>s while also overriding the button role for those in the accordion.
Running Safari and VoiceOver on macOS 10.15.2. You can hear VoiceOver applies the lang attribute to the <button> announcement, but not its aria-roledescription. Text inputs and <button>s with aria-pressed are called both toggle button and checkbox, regardless of the aria-roledescription.
Running Safari and VoiceOver on iPadOS 13.4. For links and text inputs aria-roledescription has inverse support compared to macOS. It also does not switch voices on the Portuguese, does not call the toggles checkboxes, nor recognizes the aria-roledescription on the region. This lack of parity between VoiceOver across platforms can be frustrating for users.
Running TalkBack and Chrome 81.0.4044.117 on Android 10. I paired it with a keyboard to keep the camera from changing focus as I swiped through everything. As you can hear, aria-roledescription is thoroughly ignored (as is the content in the <code> in the link, owing to an unrelated Chrome bug).
Running TalkBack and Firefox 68.7.0 on Android 10. aria-roledescription behaved as with Chrome, though as I tabbed through the page it only did it in reverse (and Shift had no affect). So that was an unrelated but novel discovery new to my set-up.


My experience with aria-roledescription so far is that it is code smell for accessibility problems. The WebAIM Million report bears this out. The First Rule of ARIA supports this.

You think you may have found a use case, and you may argue passionately for it. Until you get it in front of your users, it is nothing but a potential for technical debt and poor user experience.

Regardless, if the mis-use continues and screen readers are forced to override specified behavior as a result, you can expect this attribute to be deprecated in the future. In my opinion, that may be the best outcome given where we are today.

]]> 4
Web Sites as ‘Public Accommodation’ under a Pandemic Fri, 17 Apr 2020 13:24:54 +0000 When an organization receives an accessibility complaint about its web site, a common defense is that there are physical places available for a customer / constituent / user to complete transactions. With a brick-and-mortar available, the web site is simply an added service and so is exempted from Title III of the Americans with Disabilities Act (ADA).

To qualify, I am not a lawyer. My notes here are not legal advice, but they are worth considering for the more risk averse.

Title III of the ADA is meant to prevent discrimination based on disability in places of public accommodation, namely businesses that are generally open to the public such as restaurants, movie theaters, banks, schools, and more. It also includes non-residential privately-owned facilities — offices and factories, for example.

In Robles v. Domino’s Pizza, the Ninth Circuit Court of Appeals ruled that the web site and app offered by Domino’s are covered by the ADA (January 2019), reversing a lower court ruling. The United States Supreme Court then declined to hear the case, letting the decision stand (October 2019).

Separately, in Martinez v. San Diego County Credit Union the San Diego Superior Court ruled that its web site was not covered by Title III because of the 43 branch locations scattered around the area (February 2019). Leaning on a ruling from Carroll v. Northwest Fed. Credit Union from U.S. District Court for the Eastern District of Virginia (August 2018), both cases ruled that web sites do not count as places of public accommodation.

So here is where it gets tricky. If you dodged an accessibility lawsuit because you have physical locations, what does it mean when those physical locations close? What about when the number of locations or the operating hours are reduced?

What if that information about reduced hours or locations is available on your web site, which is itself inaccessible, as in this example from the San Diego County Credit Union COVID-19 page?

A paragraph of text announcing limited hours at branches, followed by an image link that contains the adjusted branch hours. The same content, but the image link is missing, showing the blank alt text in its place.
The credit union’s COVID-19 page showing the branch hours in an image that is also a link, followed by the same page with images disabled. A screen reader user will never hear the branch hours, and will not know the purpose of that link.

As movie theaters, restaurant ordering, college courses, and more move to online-first delivery, the notion of a corresponding brick-and-mortar venue falls away. If the current pandemic physical distancing measures stretch into the next year as many think, then this blip becomes the de facto new normal.

I am not suggesting anyone file suits against already-struggling businesses. Lodging a complaint against your local art house cinema or corner deli is a jerk move. But for those businesses who are looking to provide movie streaming services or online menu aggregation, they need to ensure the platforms they are selling to these small businesses are up to task.

For larger organizations such as banks and universities, all those accessibility efforts that were back-burner goals, lost in the backlog, need to be moved up in priority. Any organization that has a risk mitigation mindset should already be ahead of this.

Accessibility practitioners need to convey the real-world impacts on users and customers and help organizations prioritize quick wins for users. Framing the potential lost revenue and bad PR is an option, though per WebAIM’s Hierarchy for Motivating Accessibility Change I would hope we can all find a way to inspire organizations to do the right thing.

At the very least, we all need to make sure the COVID-19 information put out by any and every organization is accessible immediately. Anything else is unacceptable.

]]> 0
When Is a Vetted Pattern No Longer a Vetted Pattern? Sat, 11 Apr 2020 16:54:15 +0000 The moment you change it.

As soon as you start to tweak the underlying code or aspects of the design, you run the risk of introducing bugs. That part should be easy for any developer to understand.

The tougher part to get your arms around is that once you add features that on their own may be perfectly valid, you can create an experience that does not make sense for your users. In some cases it can be completely unusable.


I’ll grab a couple examples to try to highlight my point. I am using patterns from WAI-ARIA Authoring Practices 1.1, whose interaction is well documented, even if the testing is less so.


Let’s use a set of tabs as an example. The HTML, ARIA, CSS, and JavaScript needed to make a tab panel is generally well documented. In particular, regardless of how you choose your HTML elements and progressively enhance your tabs, the ARIA needed is very clear — at minimum you need to use tablist, tabpanel, and tab roles. Then there are various ways to assign accessible names, ensure you are conveying state properly, and build keyboard support.

You may choose to implement the tab widget pattern from WAI-ARIA Authoring Practices 1.1 (which is a note, not a standard). You may also recognize that it is not well tested and instead choose a more robust progressively-enhanced tab pattern from Scott O’Hara’s collection.

You build it, test it, get it approved, deploy it, and all is good.

Three tabs, with the middle tab active; each tab has a pin icon and the middle tab has a a red vertical pin icon. Then one day someone asks you to enhance it. They want to let users pin a tab and they want to let users do it right on the tab itself, not in the tab panel.

So you add a <button> to your tab, confirm you can get to it with keyboard, and maybe even look in the browser accessibility inspector and see it is exposed as a button. But then you discover a problem — some users cannot use it. You dig around, you find that nesting interactive controls is forbidden and then you find the tab role explicitly treats all children as presentation, stripping their semantics.

Then you decide to make the pins live adjacent to each tab, instead of within them. After all, tablist does not explicitly forbid that. But then you find your users are confused by the fact that pressing ↹Tab does not navigate tab panels, and pressing Enter seems to have no effect, or (if you do not support arrow navigation between tabs) that there are now two tab-stops to every tab.

Your once vetted tab widget is now a mess.

Listbox (Custom Select)

One of the first patterns to emerge from ARIA was the listbox, allowing developers who replaced native <select>s with their own custom code to finally make them accessible. ARIA even defines the listbox role as a replacement for a native <select>.

Presuming you make the right choice for your control, you then have to choose which pattern to use. Whether you follow the listbox pattern from WAI-ARIA Authoring Practices 1.1 (all caveats about testing apply), copy one from Kendo UI or Office Fabric (read more on these curiosities from Sarah Higley), or hate yourself enough to develop one from scratch, ideally you get one that works for your users.


A listbox with a list of options, the first option contains a pair of fields asking for a date span. Then one day someone asks you to enhance it. They want to embed a pair of date fields at the start of the options so users can filter by a date range.

So you add a pair of <input>s to your options, confirm you can get to them with keyboard, and maybe even look in the browser accessibility inspector and see they are exposed as input fields. But then you discover a problem — some users cannot use the fields. You dig around, you find that nesting interactive controls is forbidden and then you find the option role explicitly treats all children as presentation, stripping their semantics.

Then you decide to make the fields live above or below the options, instead of among them. After all, listbox does not explicitly forbid that. But then you find your users are confused by the fact that pressing ↹Tab navigates fields, but pressing arrows navigates options, and if focus is in a field then typing anything to choose an option later in the list is stolen by the field.

Your once vetted listbox widget is now a mess.

Float Labels (added 9 May 2020)

The float label pattern has become the default labeling method in Google’s Material Design design system. That means it has been taken up in Google’s Angular framework in the form of Angular Material Design components.

Angular developers can drop <mat-form-field> into a form and feel confident that no more effort is needed on their part to ensure a functional (and hopefully accessible) input field.

The problem is when those developers discover that placing the <label> after its field results in unexpected announcements for screen readers in browse mode (rare though it may be). The workaround I have seen is to retain that <label>, but hide it from screen readers with aria-hidden. Then place another <label> before the field and visually hide it. The construct then looks like this:

<mat-form-field […]>
 <div […]>
  <div […]>
   <label […] for="mat-input-26" class="sr-only">Input</label>
   <div […]>
    <input […] id="mat-input-26" aria-invalid="false" aria-required="false">
    <span […]>
     <label […] id="mat-form-field-label-71" for="mat-input-26" aria-owns="mat-input-26" aria-hidden="true">
      <mat-label […]>Input</mat-label>

Yes, that is a lot of code for a text field and its label; and that is after I removed all the Angular cruft (classes, custom attributes, inline styles, etc).

Eventually these devs have a valid reason to disable a field, and then they discover that the field is gone from browse and forms mode. This is because of Chrome’s aria-owns bug that treats owned elements as descendants in the accessibility tree (I have a Codepen showing a simplified test case).

This video shows Google Chrome’s aria-owns bug in action on Google’s Angular Material simple input example.

Your once vetted float label is now a mess.


Obviously these two examples are technically fixable. While you may be ahead of the game by re-using an existing component as your base, and adding features, you must consider that you are also introducing code that can result in bugs. In addition to bugs, you are introducing changes that can be confusing to users.

Should you go down this path, be sure to build in enough time to run through a full testing suite — code validation, accessibility, testing with users, security, and so on. You may have reduced your development time, but not your testing time. You have to treat it as completely new.

A robust pattern should account for localization, different contexts, different use cases, and so much more. Your base pattern ideally already has that, but if you are generally unfamiliar with all that the original developers (should have already) considered then I have a short list of basic custom control requirements that also links to other resources and checklists for reference.

]]> 2
I Don’t Care What Google or Apple or Whoever Did Tue, 24 Mar 2020 12:18:31 +0000

Please do not use this post as an excuse to beat up the devs at Apple or Google. If you are doing that, you have missed the point of this post and you are being unnecessarily mean to individuals who may have no control over broader organizational decisions. Do not be that guy / person / jerk.



Be Best







I may have ranted about this in a Twitter thread:

Update: March 26, 2020


]]> 7
Chrome 80/81 Bug: Accessible Name Calculation Thu, 19 Mar 2020 21:42:42 +0000 The good:
Chrome 80 rolled out on 19 February 2020, and with it came a pile of fixes for how elements with CSS display properties have their semantics exposed in the accessibility tree. These huge accessibility bug fixes featured prominently in my CSUN talk this year (starting at slide 36).

The bad:
Unfortunately, that release introduced a new bug, filed 30 January 2020: Issue 1047549: Incorrect name calculation for ListBoxOption with strong or emphasis text

Add in some of my testing, asking around, wading into a WebAIM thread, and what I found is that some HTML elements nested within <label>s are excluded from the accessibility tree. I am probably over-simplifying, but at the very least it is clear accessible name calculation in Chrome 80 is kinda broken and extends well beyond the listbox scope of the bug report.

The ugly:
The fix came too late to make it into Chrome 81 (per the bug report). With Chrome 81 now delayed, any fix coming in Chrome 82, originally scheduled for 9 June 2020, will come at some yet-to-be-determined future date.

That means this post has a freshness date. After some time in June or July 2020, I hope you can ignore this post.


I discovered this when testing a form on a project. A form I had tested before with a screen reader and which had already been buttoned up and deployed. I made a crude minimal representation of it on Codepen (view it in debug mode to remove Codepen cruft for testing).

See the Pen Testing Chrome 80/81 Bug by Adrian Roselli (@aardrian) on CodePen.

The simplest way to confirm the problem is to open the dev tools and pop into the accessibility inspector. When you look at the accessible name for each control, the text that is wrapped in <strong> and <em> is completely excluded.

Accessibility inspector showing the computed name for the first radio button is “I am infected”, matching its visible text. Accessibility inspector showing the computed name for the second radio button is “I am infected”, not matching its visible text of “I am not infected” where the word “not” is bold. Accessibility inspector showing the computed name for the checkbox is “I affirm my statement above is true”, not matching its visible text of “I affirm my statement above is true and that I waive all HIPAA protections and authorize you to notify my employer” where the second clause is italic.
Screen shots from Chrome 80 dev tools showing the accessible name for each of the two radio buttons and one checkbox.

It may be a bit hard to understand just how much the meaning can be changed by excluding a single word or clause, so I made a video.

Running Chrome 80.0.3987.132 and JAWS 2018 on Windows 10. The first part of the video is me letting JAWS read through the page. The second part of the video (17 seconds on) is me using Tab to interact with the form.

I also confirmed the problem in Chrome for Android.

Text caption from TalkBack with Chrome showing the computed name for the second radio button is “I am infected”, not matching its visible text of “I am not infected” where the word “not” is bold. Text caption from TalkBack with Chrome showing the name for the checkbox is “I affirm my statement above is true”, not matching its visible text of “I affirm my statement above is true and that I waive all HIPAA protections and authorize you to notify my employer” where the second clause is italic.
Screen shots from Chrome 80 on Android 10 showing the caption from TalkBack and demonstrating the bold and italic text are not announced.

On the WebAIM list someone pointed out that a ‘fix’ is to add role="presentation" to each element that is not being announced. Doing so appeared to do the trick. So, yeah, that works, but it is a terrible, awful hack.

Other reports indicate <code>, <b>, and many more on all also have the same problem when part of an accessible name. Which means, essentially, you have to test.


I don’t know how this happened. I am not dunking on Chrome nor its team. Building a browser with a 6-week life-cycle is hard work. Bugs happen. This just happens to be a bug with potential massive risk for screen reader users.

This is not something an automated accessibility checker would catch. As far as I know, none compares the DOM with the accessibility tree to look for discrepancies. A validator would not catch this, either. This is something you would only catch with manual testing.

Probably do not rush out and add role="presentation" to every inline element that is part of an accessible name. My testing was not that thorough. It would also hobble the experience for browsers that aren’t broken (Firefox). Instead, find those critical forms and screens and spot test them. Then make a decision based on that context and your user stats.

Understand that this bug will be in the wild for at least three more months (late June 2020), perhaps more depending on how far the Chrome 82 release slips. Which you will have to test again.

Update: 1 April 2020

Chrome 82 is being skipped and 83 is being moved up by three weeks, though this appears to be just a naming/semantics issue as it will contain the 82 changes.

The bug report got some traction since I first posted this, and the specific code fix (dated 3 February) is referenced for a merge into the M81 / 4044 branch.

If the updated schedule is accurate, Chrome 81 will drop on 7 April. While I have not heard back on a question about when to expect this, it seems like I should test the release next week if/when it drops.

In the meantime, continue to test your critical applications for the impact of this bug.

Update: 16 April 2020

Good news, everyone! I have updated to Chrome 81 and the accessible name in my examples is no longer broken. If you test my minimal representation of it on Codepen (maybe easier in debug mode) and inspect the accessible name of each control, you will see the text in bold or italics is now represented in the accessible name.

Of course, your users may not have updated. I say this as I wave my hand generally in the direction of a few thousand financial institution employees near my house and who are on machines where Chrome updates are managed via a security policy and will be unable to update immediately.

So still be certain that for now you are not embedding critical information in any additional inline elements when setting an accessible name for a thing.

Update: 21 April 2020

This is not fixed in Chrome 81.

My test case was too limited. While testing another experiment of mine, I discovered that my buttons with text wrapped in <code> were not being used for the accessible name.

I have updated the embedded pen above to contain a <button><code></code></button> construct. You can slice and dice the code as you see fit to test it with other elements, but it is pretty clear this larger accessible name calculation bug persists.

While the bug I cited above was closed as fixed, I opened a new bug to report this: Issue 1073026: Incorrect name for button with text in <code>

Update: 22 April 2020

The bug I filed was merged overnight with Issue 1071827: Accessible name of buttons is not computed correctly when using only css content.

While that issue details a bug in Chrome when using CSS generated content in accessible name calculation, the larger message is that Chrome’s accessible name calculation is regressing and we all need to be diligent about testing.

]]> 3
CSUN 2020: CSS Display Properties versus HTML Semantics Fri, 13 Mar 2020 17:25:15 +0000 While originally I was scheduled to attend CSUN to present two talks one talk, I ended up not attending (my father won A Major Award so I joined him instead). I gave my talk remotely in my scheduled slot (during my father’s award dinner).

While I would have loved to have been there, the COVID-19 cloud hung over the entire event, with plenty of sponsors, speakers, and attendees taking precautions and skipping. As it is, when California recommended a ban on large groups, that may have helped motivate CSUN to wrap early.

Those who attended my talk got to see it live. For those who did not attend my talk, or opted to skip CSUN overall, I have posted my slides.

Or visit them directly at SlideShare: CSUN 2020: CSS Display Properties versus HTML Semantics

The typefaces are messed up because I used a pre-built PowerPoint template and the fonts it uses do not translate well to SlideShare.


There were a few slides of tables showing browser and screen reader support. The structure is lost in the SlideShare embed, so I have re-created them here.

Most common screen reader and browser combinations
Screen Reader & Browser Number of Respondents Percent of Respondents
JAWS with Chrome 259 21.40%
NVDA with Firefox 237 19.60%
NVDA with Chrome 218 18.00%
JAWS with Internet Explorer 139 11.50%
VoiceOver with Safari 110 9.10%
JAWS with Firefox 71 5.90%
VoiceOver with Chrome 36 3.00%
NVDA with Internet Explorer 14 1.20%
Other combinations 126 10.40%
Slide 39, WebAIM Screen Reader Survey #8 (2019)
Chrome 80 / Windows 10
CSS <table>, etc. <ul>, <ol>, <dl> <h#> <button>
display: flex
display: grid
display: block
display: inline-block
display: contents
Slide 40
Firefox 73 / Windows 10
display: flex 1
display: grid 1
display: block
display: inline-block 2
display: contents 2 3
Slide 41
  1. <th>s with flex and grid are announced as cells, not column or row header;
  2. Inserts text leaf between each one;
  3. Issues a keyboard use warning.
Safari / macOS 10.15.2
display: flex
display: grid
display: block 1
display: inline-block 1
display: contents 1
Slide 42
  1. But recognizes <dl>.
Chrome 80 / Android 10
display: flex
display: grid
display: block
display: inline-block
display: contents
Slide 43
Safari / iOS
display: flex
display: grid
display: block 1
display: inline-block 1
display: contents 1 2
Slide 44
  1. Treats all cells belonging in column 1;
  2. Still fires on double-tap with VO.



Slide 20
Slide 29
Slide 30
Slide 53
Slide 64



The room was mostly full (or so I was told) and a few of those attendees even tweeted about my session.

]]> 0
Stop Using ‘Drop-down’ Sun, 01 Mar 2020 20:28:34 +0000 TL;DR: Stop using the word drop-down. Instead choose a term that accurately describes the control you want.

Illustration of IKEA stepping into a chest of drawers that is not secured to the wall, so it is falling on him.

I have worked both with native platform developers and web developers. While control names might differ, if a control was functionally the same then it was not an issue. A TextBox, for example, translates pretty easily to <input type="text"> in HTML. Or at least in 1995 it did.

I’ve also worked with ad agencies as they spun up their web teams. Translating their HyperCard or Director skills meant their lingo also worked its way into web projects. But we could still posit that a hypertext region in a design comp was probably a link in HTML.

The word drop-down (dropdown, drahp-daughn, daft-dolt) was common with both sets of clients. However, if you used the term with one group, you might get a specific control that does not translate to the web. With another group you might get a design that makes a specific control impossible to build.

With broken processes that didn’t get everyone in the room at the same time and without a common understanding of drop-down, we ossified into our current situation.

As you embark on a design or build or specification, it is important to understand what you are producing and why. When you say drop-down, which one of these things do you mean? What about your client? Your designer? Your developer? The user (who calls the help line)? The screen reader? The voice control software?

1. <select>

Tip If your intent is to use a native HTML <select>, then call it a select.

It most accurately describes the technical implementation which also describes the customization challenges. Though the design challenges may not be as challenging as some think.

If describing the control to a screen reader user, it may be helpful to know that VoiceOver on macOS & iOS and TalkBack on Android will refer to a <select> as pop-up button. JAWS and NVDA refer to it as combobox.

2. ARIA Listbox

ARIA defines roles for composite user interface widgets, which are generally containers for the necessary parts of a complete widget. The listbox role corresponds to the native <select> element. The listbox‘s required children have the option role, corresponding to the native <option>.

Tip If you are referring to this construct, call it an ARIA listbox.

The term sets expectations for the implementation and implies a design not constrained by browser defaults the way <select> is. There is more than one way to implement a control that uses the listbox role, with the critical difference being the trigger. See the articles linked at the end.

3. <datalist>

Despite screen readers on Windows referring to a <select> as combobox, the word combobox has a very specific meaning especially to old-school Windows forms developers. In web technology terms, it is analogous to a <select> with a text field.

Until recently, HTML had no equivalent to a native combobox, but the <datalist>/<option> roughly addresses that. Screen readers seem to enjoy announcing it differently depending on, well, reasons — listbox pop-up on iOS, text field on macOS, edit box on Android, type in text in JAWS with IE11, combo in JAWS with Chrome, text pop-up in JAWS with Firefox, and has auto-complete with NVDA.

Tip To refer to this pattern, call it a datalist.

This will also signal to developers and designers that you are leaning on browser defaults, and therefore manage expectations for visual representation.

4. ARIA Combobox

If you are looking for a combobox, it would be a good idea to identify whether you want it developed with ARIA or with native HTML (see <datalist>). How much custom design is required will probably influence your choice.

In unsupporting browsers, the ARIA combobox role can fill that gap when paired with the required textbox role and (generally) the listbox role.

Tip To refer to this pattern, call it an ARIA combobox.

The term sets expectations for the implementation and implies a design not constrained by browser defaults the way <datalist> is. You may still need to do further testing to identify if you want the ARIA 1.0 combobox, ARIA 1.1 combobox (linked above), or draft ARIA 1.2 combobox.

5. Autocomplete

If the control is meant to provide users with suggestions that may not be available in the DOM already, then you will need to clarify further.

If the request is for a control with the autocomplete attribute (perhaps to satisfy WCAG SC 1.3.5: Identify Input Purpose), then that is not a distinct control. That is a feature that the browser can support on existing <input>, <select>, or <textarea> fields.

Otherwise, presuming the previous options are not a fit and the requirements call for an XMLHttpRequest to populate options as you type, then you have a different kind of control. Alternatively, if the control supports matching values the user does not type (such as typing a word in one language and seeing a result in a different language, as in this autocomplete example), then you also have a different kind of control.

Tip For this scenario, knowing you may still need to gather requirements, call it an autocomplete.

If you are trying to recreate a native menu, as you would get when you right-click with your mouse, then the ARIA menu role is your choice. If you want a native menu similar to what youfind in a Windows or Mac application then the menubar role is your pick.

Tip You can get away with referring to either option as an ARIA menu.

When discussing this option, it must be clear that an ARIA menu is for replicating native OS features, not for web site navigation or other general web content purposes. If you need help making your case, I have written in detail at Don’t Use ARIA Menu Roles for Site Nav.

7. <details>/<summary>

While <details> and <summary> support in browsers is relatively recent (and absent in IE and legacy Edge), this a handy native element for hiding and showing content. For unsupporting browsers you will still need JavaScript for a polyfill.

Effectively <details>/<summary> are a native disclosure widget. That’s about it. <details>/<summary> are not a lot of other things (I thought them not being a modal was obvious, but people gotta experiment).

Tip You can refer to these generally as details / summary, or details & summary.

A competent web person will know you are talking about an interactive widget, not a form control.

8. Disclosure Widget

Think of this as the ARIA version of <details> and <summary>, but in this case you are not using ARIA roles to enhance generic controls. You need a <button> with aria-expanded (and aria-controls despite falling support) and an associated content container whose visibility is toggled.

The disclosure widget at the ARIA Authoring Practices document is one of the few mature patterns in that note, and you can use it as the basis for your code. The pattern is robust and can even be used for navigation.

Tip Use the term disclosure widget to refer to this pattern.

9. Accordion

Similar to the disclosure widget pattern, there is no accordion role defined in ARIA. Unlike the disclosure widget, there is no corresponding native HTML element. The ARIA tab pattern, even the vertical one, is not a fit because tab panels do not collapse to reclaim space. That being said, ensure your need does not map to the ARIA tab pattern.

The accordion widget at the ARIA Authoring Practices document is one of the other mature patterns in that note, which you can use as the basis for your code. You need a <button> with aria-expanded (and aria-controls despite falling support) within a header and an associated content container whose visibility is toggled while also toggling the visibility of the other content containers.

Tip When discussing this pattern, call it an accordion.

10. Fly-out Navigation

Web site navigation has been referred to as drop-downs for years. For evidence of how common it is, and its age, the Suckerfish drop-downs were released in 2003 and leaned on an already common term.

If you are looking for navigation where tabbing or mousing through the top-level options reveals nested options, then we need a more meaningful term. The WAI provides tutorials for assorted patterns, and the Fly-out Menus fits. It is not a new term, existing since before Suckerfish.

Tip If you have navigation that generally fits this pattern, refer to it is fly-out or fly-out navigation.

11. Custom Display Selector

This is sort of a catch-all. I have seen developers come up with all methods of making controls that essentially work by hiding some stuff, then showing that stuff by animating or popping some container into existence. These are generally inaccessible and this might be a great opportunity to make sure you choose something that conforms to one of the patterns above.

If you find yourself venturing into this territory, probably stop. Go back and figure out what the requirements truly are, as opposed to relying on assumptions or a weird desire to invent a new pattern that nobody will understand.

Tip When discussing this approach, call it consulting.

I suspect someone might mention <menu> and <menuitem>, but those elements are deprecated for lack of implementation. I refer you to ARIA menus above if you think you have a use case.

I have heard the term pop-up in place of drop-down. Pop-up is an even more confusing term since it can also mean a tool-tip, a native dialog, a modal dialog, or even a new window (remember pop-unders?). This is not helped when screen readers refer to some <select>-like controls as pop-ups.

If someone asks for a pop-up, you first have to disambiguate (hey, a Wikipedia word!) between a dialog-like thing, a form control, or display widget thing. Then you need to be diligent in what terms you use.

The term menu also gets tossed around when referring to a <select>, combobox, or navigation, all of which contributes to the confusion. There are arguably fewer potential meanings for menu then for drop-down, but you can apply the same logic to try to pare down what anyone means who uses it.

For a deeper dive into the <select>, listbox, or combobox patterns, particularly if you plan to implement any of them, I strongly recommend the following articles by Sarah Higley:



Update: 16 April 2020

In the Nielsen Norman Group post, Listboxes vs. Dropdown Lists, the author describes the difference between two controls based on their visual affordance. Given that the article is talking about these concepts generally, it’s fair to not expect it to use the specific technical terms.

However, in a web context their dropdown example maps to <select> or an ARIA listbox.

Their listbox example maps to <select size="[>0]">, <select size="[>0]" multiple>, side-by-side versions of these controls (for moving items between them), or a collection of checkboxes in a container that may or may not scroll. Or it can map to an ARIA listbox.

The cited example is the product filters on the Sephora site, each of which can be a <fieldset> / <legend> construct with a fixed height that allows scrolling and contains checkboxes.

There is a table at the end of the post to help you identify which of the two patterns may be a good fit for your needs. However given the potential difference in interactions between the possible five distinct ways of coding these, it fails in interaction guidance. For example, is one pattern more cumbersome for a screen reader user? How should one choose when considering keyboard-only users?

From a user experience perspective the article is a handy reference for initial discussions. From a development perspective, it misses an opportunity to disambiguate and offer guidance on specific interactions, potentially continuing the confusion about what these names mean.

]]> 46
Role-up Sun, 23 Feb 2020 03:20:56 +0000 transitive verb
[ ‘rōl əp ]
roled-up, roleing-up, roles-up

Definition of role-up

  1. To use an ARIA role on an HTML element to change its semantics and/or force it to accept an accessible name (via aria-label, aria-labelledby, or even aria-describedby).
  2. To add ARIA roles everywhere without understanding, often via framework or library.

Example Sentences

Let’s role-up that <abbr> as an image.

The framework roled-up every <table> as a grid.

When roleing-up a link as a button, remember to add a Space handler.

Ed always roles-up <footer>s with contentinfo; he also wears belts with braces.


cast, re-cast

Use in Pop Culture

]]> 0
Block Links, Cards, Clickable Regions, Rows, Etc. Mon, 17 Feb 2020 16:09:38 +0000 Whether you call them cards, block links, or some other thing, the construct of making an area of content clickable (tappable, Enter-key-able, voice-activatable, etc.) is not new.

While hit area size is mostly a usability issue, marketers often want a larger click area around their calls to action (CTAs) to make them more prominent. Without thinking about the impact on screen reader users, that can make for extremely verbose controls. For voice users, it can be confusing what to say to select one of these.

There are no earth-shattering techniques here. I am mostly demonstrating how doing it the wrong way can be a problem and how a little planning can make the better way less awful.


If you fail to make the link to display: block, it will have dead areas around words or chunks of text, which can make them feel like distinct links for a mouse user. If you use an underline on the entire block of text, it can be hard to read, while no underline at all can remove that visual affordance.


Tabbing through the links in Firefox 72 with NVDA 2019.3.1.

The other two links wrap either the <h3> (second example) or the Read more… text (third example). For the third one I use aria-describedby to point to the <h3> for more context on the link, but that is not the only nor best way to do it.

The following example demonstrates arrowing through the content. Since there is a misconception among developers that screen reader users Tab through everything on a page, they often forget to test the primary way screen readers scan a page — by arrowing (or swiping if on a touch device).

Arrowing through the content in Firefox 72 with NVDA 2019.3.1.

You can hear how every chunk of text is announced as a link, and it is not clear that they are all the same link. Only a more skilled user is likely to recognize the pattern and guess they all go to the same place without further inspection.

The CSS for the block link is generally straightforward:

.block a[href]::after {
  content: "";
  display: block;
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;

Note that this approach prevents selecting the text.

Heydon Pickering goes into more detail on cards in Inclusive Components, which generally covers the same stuff as here but without the screen reader examples.


I get asked about two challenges to this approach regularly. Content order and additional controls.

Content Order

Because I feel strongly that headings should be used to structure your content (content following a heading contextually belongs to that heading), some developers may not know how to mark up a design where an image visually appears above the heading. After all, if the image is in the card, it probably belongs to the heading and text in that card.

Side-by-side comparison of the same card with the image moved to the top in the second one.
The only difference between these two is visual. The image is moved up using CSS, but in the DOM still follows the heading.

This is one of the few cases where I advocate using CSS to re-order the content to achieve the visual design. If you use my reading order bookmarklet on this, you will see the image is moved above the heading.

The CSS:

.reorder {
  display: flex;
  flex-direction: column;

.reorder h3 {
  order: 2;

.reorder p {
  order: 3;

If you only set order: 1 on the <img> and none of the other content get an order defined, then the <img> will appear after the other content.

Content Order — Updated

As Dannie notes in the comments, I could have used the following CSS instead:

.reorder {
  display: flex;
  flex-direction: column;

.reorder img {
  order: -1;

I adjusted the Codepen example as well. I did not thoroughly test this, either. I cannot say if I just forgot about using a negative value or purged it from working memory due to some issue somewhere, so of course test.

Additional Controls

If you need to have an additional link, or a button, or some other control within the card and the design places it at the bottom or top (or even along a side), you can account for this by leaving space for it. This or similar CSS may do the trick:

.block.extracta a[href]::after {
  bottom: 3.75em;

.block.extracta p a[href]::after {
  content: none;

In my example, I am basing it on the the current text size (em units), but you may need to get more creative if your content will be unknown to you, translated, localized, or otherwise variable. My example is sloppy because I have such a broad selector for links in my cards, so I have to remove the generated content from the additional link.

Side-by-side comparison of the same card with the clickable area highlighted via browser dev tools.
The dev tools show the clickable area of the block link, leaving a gap at the bottom.

In the image, see that I leave some dead space above the other interactive control. Ideally this can help prevent mis-clicks/-taps.


Most of what I review above applies to <button>s as well. As more and more web content tries to be more appy (think SPAs), a native button might be used to change settings or preferences. Dashboard pages often include card-like interfaces, and those cards similarly stuff way too much into a <button>.

Unlike a link, which allows nested headings, lists, and so on (regardless of how verbose that is), nesting structured content within a <button> removes that structure. An <h3> inside a <button> is no longer a heading. It is just a piece of text. The following video demonstrates that in action.

Tabbing through the buttons in Safari with VoiceOver on macOS.

Arrowing through the content of a <button> does not mean that structure will be exposed either.

Arrowing through the content in Safari with VoiceOver on macOS.

A user familiar with the screen probably is fine with a control that announces only as Settings. Even if confused, it is a simple (and faster) process to arrow backward or jump to the preceding heading for the needed context.

The styles I used to make block links generally apply here. If for some reason your button has an image or needs to have another control within its visual container, the tactics I used above also generally apply.


All the code for the examples is embedded below. You can visit the Codepen directly to play with the code, or view it in debug mode with your own assistive technology.

See the Pen vYOGpXb by Adrian Roselli (@aardrian) on CodePen.

Update: February 23, 2020

At the end of this post is a comment asking about Smashing Magazine’s home page. While I responded to the question, images do a better job.

A single article abstract on the Smashing Magazine home page. A single article abstract on the Smashing Magazine home page showing all seven links.. A single article abstract on the Smashing Magazine home page showing the article link overlapping all 7 links plus a contrast and scroll-to-top link.
The first image is a single article abstract from the home page. The second image shows the seven (7) distinct links in that card layout (there can be a minimum of 6, and the number climbs with each category). The third image shows the effects of a block link using ::before that overlaps all seven links as well as the page’s contrast widget and scroll-to-top feature.

In my response, I note that the simplest fix for this problem is to remove the ::before on the <a> around the article title. This would help average mouse and touch users. It can also be a boon to mobility-impaired users and blind users who may be using explore-by-touch in their mobile screen reader.

Update: March 31, 2020

I was asked about clickable table rows, which are a particular annoyance to many screen reader users. The current practice across libraries is to attach a click handler to the row, which means everything in the row announces as clickable.

We can adapt the block approach to work with rows. Unlike cards, you want to the control’s clickable area to extend beyond its parent container, which is a cell.

In the following example, the width is generally known (100% minus the 1vw margins) and the tables are rather wide. While not perfect (on wide screens) this at least works for the example and shows the concept.

There are two tables. The first uses links in the first row, and the second example uses buttons in the last row.

Visit the tables example directly at Codepen or view the tables example in debug mode and test with your own assistive technology.

See the Pen Block Table Rows by Adrian Roselli (@aardrian) on CodePen.

Update: April 1, 2020

Great point about selecting text from cells:

I mention the copying caveat elsewhere, but specifically on this point:

As always, context matters, test with your users.

]]> 12