Accessibility as a design pattern

Ismayil Khayredinov
ITNEXT
Published in
7 min readJan 9, 2022

--

Image source: 3playmedia

We often think of accessibility as something that requires extra development effort, treating it as a good-to-have feature, aimed at a small subset of users with special needs. But such line of thinking misses the mark and we fail to appreciate how accessibility standards can improve our day-to-day experiences with building front-end interfaces, leading to healthier more readable code, cleaner tests, and better overall architecture of our applications. Not to mention that it benefits all users in equal measure.

In the last few months, I have been trying to understand, why front-end developers (myself included) hate writing tests, and why we are so adamant in our belief that “front-end testing is hard”. My explorations have converged on a subject of accessibility, and ARIA in particular, as a missing ingredient that would make our experiences with testing our code more enjoyable.

The Evil of Ambiguity

As developers, we spend a lot of time thinking about naming conventions — in essence, we value properly named variables and functions as they reduce ambiguity, allowing us to clearly communicate our intentions to everyone else reading or using the code. Type-safety is another example of disambiguation - it ensures that our code is interpreted as intended, leaving little room for assumption and misuse.

While we are so focused on the code itself, we don’t often give as much consideration to the resulting output of what this code produces. We write beautiful React components that follow established design patterns and strict naming conventions, we cover them with unit tests, but we are less concerned with how ambiguous the HTML output of these components is, especially when they are composed together on a page.

Part of the problem is that we are working with CSS, which provides a certain level of visual disambiguation — spacing, borders and box-shadows allow us to deconstruct the page into logical units, which have an implicit semantic meaning. Using visual boundaries, we know, where an element begins and ends, and we can interact with elements we intend to interact with without getting confused by blocks of meaningless text.

Remove all the CSS, however, and you are left with a bunch of spaghetti HTML that neither yourself, nor the screenreader, nor the test runner can make sense of.

The Joy of Readable Code

We love issuing commands to our voice assistants using natural language. Code is nothing but a series of commands we give to a computer, so naturally the most readable code is the one that approximates human language.

In my thinking about front-end testing, I started drawing parallels between writing Cypress tests and using Alexa to navigate a website. If there were an Alexa skill for interacting with web-pages, it would use an engine similar to Cypress with a testing library on top of it.

— Alexa, find a Login form
— Alexa, enter john.doe in Username field
— Alexa, enter secret in Password field
— Alexa, press Submit button

The same can be expressed in code:

With some syntactic sugar, we can make the test read like natural language.

We can take it one step further, and map each command to a Gherkin keyword (this would require some instrumentation to ensure that once a landmark has been “seen”, the consecutive calls are executed within its boundaries).

Of course none of this is possible on a page that has a form styled like a form but doesn’t express the same semantics through HTML code. It can look like a duck, quack like a duck, but it could be just a styled cuckoo.

The cornerstone of accessibility is cross-device interoperability of user interfaces, and to achieve it, we must first ensure that our interfaces are machine-friendly and are not based solely on the subjectivity of comprehension that is affected by physical characteristics of individual users and determined by the visual appearance of the application alone (at least not until the artificial “intelligence” is intelligent enough to infer semantics from appearance).

Conveying Meaning with HTML

As noted above, CSS is a powerful tool that can help us convey meaning, but relying on CSS alone is fallacy. First of all, CSS is media-specific, and common sense dictates that you shouldn’t ship CSS to devices that can’t render it. Secondly, you should maintain separation of concerns and CSS is meant for styling and theming — no matter how hard you try at BEM it’s not the right way of contextualising your HTML markup (besides all that goes out of the window if you use CSS modules). data- attributes are powerful and can help shim certain use-cases, but we already have a wide range of tools at our disposal, including HTML itself and ARIA guidelines, designed by a bunch of smart people, who see the bigger picture. Standards are deterministic and assertive, so they can easily be expressed as testing commands. For example, this ARIA guideline:

The required context role defines the owning container where this role is allowed. If a role has a required context, authors MUST ensure that an element with the role is contained inside (or owned by) an element with the required context role. For example, an element with role listitem is only meaningful when contained inside (or owned by) an element with role list.

can be expressed in pseudo code as:

expect(listItem).toBeOwnedBy(list);
expect(list).toOwn(listItem);

Test-driven development is a fine approach, but it’s not easy without an established methodology and a command framework. Test-driven thinking should probably precede TDD — design your tests before your write any. Work out how you want your tests to read first, create utilities to achieve that, and write your components in a way that satisfies the testability criteria. All too often we write integration and e2e tests retrospectively, which end up reading as if we were testing our CSS selector naming conventions. Also remember that for complex use cases, you can export Cypress commands from your component libraries, and import them into your application as a plugin (just like the testing library does).

A test is a contract with your user, not with the user’s screen, and what matters is that the user can have meaningful interactions with the application, regardless of what device they prefer to use on a given day. Wouldn’t it be great to have a command-line tool for interacting with a website?

hj list forms http://mysite.com
1. Login
2. Register
3. Newsletter Signup
hj fill Login
Username:
Password:
Submit?

The test case we looked at above is quite straightforward in terms of HTML markup.

The problem however is that we don’t know that this particular form is a login form. If we add a registration form on the same page, we are running into an issue of ambiguity — we can no longer distinguish between the two forms as they have a similar set of fields. We could add ids to forms and form elements, or use CSS selectors, but that wouldn’t solve the problem with ambiguity — someone on a screenreader still wouldn’t know the difference.

Labelled Landmarks

We can disambiguate our interfaces by providing meaningful labels to all landmark roles, such as form, list, region, dialog etc. HTML and ARIA standards allow us to use <label>, aria-label, aria-labelledby to achieve that.

Let’s disambiguate an infamous To-Do list:

We can now interact with our list without any problems, making it easy for anyone to code review the tests (without having to know the implementation details of the component). We don’t need the hard-to-read IDs, data-attributes, CSS selectors, n-th child selectors, etc. An additional benefit is that devices without screens can interact with the interface just as well, as your test runner can.

Relational Markup

HTML on its own relies heavily on hierarchies to convey relationships between elements. Using ARIA, we can build more complex semantic relationships between elements.

As an example, let’s take a look at contextualising errors, as it is a common-enough use-case that leads to complex selector juggling in tests.

Let’s build a file upload component that stacks the files into a list, validates the file type and shows an error if something fails validation.

Using ARIA we can build relationships between elements, which can then be used to abstract repetitive queries, as well as complex queries for elements, which are not direct descendants. In the example above we are indicating that both the input and the line item are described by the same error that relates to the second uploaded item, so it becomes easier to deal with assertions in the tests.

Combining ARIA roles, semantic HTML tags, and defining relationships between elements, we can start giving meaning to our markup, making it easier to target elements in our tests, while enhancing the experiences of our users.

Does your spinner mean anything without the CSS? Does the user know that something is happening on the page if they can’t see the screen? Do they know that they have made a mistake, or that it is now safe to submit the form with all their files uploaded?

Statefulness and Readiness

Retriability in Cypress is a very useful feature, but it also makes us forget to wonder, how would a user know that after their action something has happened and something else is ready to be interacted with? For instance, how would a user know that the dropdown has appeared and they should make a selection, if they can’t see the screen?

We can now make sure that the popup’s visibility is properly communicated to the user (without presuming that if the Cypress can retry it, so can the user).

Conclusion

I am leaving you with two terms to ponder: Test-Driven Accessibility and Accessibility-Driven Testing.

Embracing ARIA as a testability criteria is a great opportunity to drive accessibility of our applications, while improving our own experiences with testing. Of course there is a lost more to accessibility than labels and relationships, but we have to start somewhere.

ARIA specifications are designed with assistive technologies in mind, but the fact remains that a test runner isn’t that different from a screenreader — both rely on markup to do their job, and with a bit of semantics both can excel at it.

Moving away from CSS selectors and custom data attributes, and relying more on established ARIA guidelines offers a great opportunity to disambiguate the HTML markup, improving usability of the code base as well as it’s final outcome, with a multitude of experiences that diverse user groups have with it.

--

--

Full-stack developer, passionate about front-end frameworks, design systems and UX.