Harvest as a Web application is now about 15 years old, which is about the same amount of time I’ve been working on the Web(!). Over that time, the medium has changed so much that I barely recognize the best practices from my early days. As the Web has become more capable, Harvest has become more capable alongside it. Static pages have slowly become more dynamic, and new paradigms require new accessibility practices and quality assurance.
This year, we’ve started a renewed effort to unify Harvest’s design and make it more consistent, providing standard components for developers to compose. In the process, we saw an opportunity to ensure that the components we use are as accessible as possible by default and that developers can take advantage of them even without a great depth of accessibility knowledge. In this post, I’m going to try to highlight a few ways we’re going about it and the varying levels of success we’ve had.
I’m not an expert in accessibility. In fact, I’ve often found it to be a daunting topic. There’s an enormous depth and breadth of information to be found and some of it is pretty difficult to implement. For me, the best way to make progress is to make it incrementally with small changes so I’ll point out some examples of things we’ve improved in Harvest. While we have made great progress, we’re still working on accessibility. Many areas of Harvest are still improving and these are some of our successes so far.
Before I get into the weeds, I would like to thank Accessible 360, a customer of Harvest that has kindly provided valuable feedback about areas we could improve. We really appreciate their input and that of anyone else who would like to point out more examples to us.
When in doubt, check it out
Any time you’re uncertain about an interaction, I encourage you to fire up a screen reader and try it out using only the keyboard. This is what gives me the most confidence about making changes. I use the macOS Voiceover Utility app, which you can turn on with CMD+F5, but you can use whatever is appropriate for your platform.
There are many different screen reader apps, but getting a feel for any of them is much better than nothing. Just dive in and get your feet wet. Ask questions about the level of difficulty accomplishing tasks. I’ve often found that this exercise improves my awareness of UX in general and makes me more likely to notice them even without the screen reader.
- Which tasks are difficult to accomplish?
- Why were they difficult to accomplish?
- How could these tasks become easier to accomplish?
Ok, now let’s jump into specifics a little bit.
Let’s start with something small: labels! One issue we noticed when auditing Harvest was that many actions lacked an appropriate label. For instance, alerts about various actions in Harvest contained a button with an X icon, but no other context. This is fine if the user is familiar with the interaction and can see the X, but using a screen reader it’s not clear what the button does because it’s not labeled. In this particular case, we decided we have plenty of room to be explicit so we used the text “Dismiss” instead. This means that the action is accessible without any additional attributes, and it’s also more clear what it will do in general. Win-win!
That said, there are some areas that still include icon-only buttons that we haven’t gotten around to redesigning yet. There are also areas where icons might be appropriate, but not accessible. In these cases, we add the “aria-label” attribute to make sure these controls are accessible. The “aria-label” attribute is used in cases where there is no visible text labeling an element so that screen readers can assign a name to it. For instance, we often use an aria label of “Edit” for icon-only buttons in Harvest.
For example, the edit button in the Expenses section of Harvest has an aria-label attribute value of “Edit”. This makes it clear that you can edit the expense by clicking it.
Adding labels to elements that are missing them is a relatively simple process but we’ve found it to be an impactful way to improve the experience when using a screen reader.
I’m gonna take a quick detour here to talk about testing. We’ve found that end-to-end tests are the best way to make sure things don’t revert to inaccessibility over time. In particular, many test frameworks include an option to find elements using aria attributes or by querying the accessibility tree. At Harvest, we use Capybara finders in our Rails projects and puppeteer selectors in our Node projects.
In addition to keeping our interactions accessible, testing with accessibility in mind tends to create more resilient tests. We’ve slowly moved away from locating elements by class, id, or data attributes and towards using text labels to write our tests. The former can often change during a refactor, but the latter usually do not and that means our tests can often survive a refactor untouched. This leads to greater confidence in our tests and a willingness to take on more ambitious refactoring! 🎉
As an added bonus, this makes our tests very readable! Instead of
find('#nav > button:first-child').click, we’re writing
click_on 'Home'. This is a big help when trying to decipher some tests you didn’t write.
Ok, back to some examples…
Unsurprisingly, accessibility on the Web is often about focus. What element is currently focused? What element will be focused next? How does the currently focused element interact with the screen reader? In general, we want to direct the focus in a way that makes sense for each interaction. It’s not always clear exactly which element should be focused next, but it’s generally clear when focus is shifting to a completely unrelated area. This is where we can fix things!
Be kind, please rewind
When navigating via keyboard, some of the most jarring interactions are in components like dialogs and dropdowns that move focus out of the normal flow and into a separate element elsewhere in the DOM. The problem is that once the interaction is complete, focus is not returned to the initiating control, forcing you to tab through the entire page again before returning to your task.
If a button on the page opens a dialog, closing the dialog (which you should be able to do with the escape key) should return focus to that button so you can continue in the flow of the document. When pressing a button in a dropdown menu, focus should return to the menu trigger. In the following example, closing the menu or clicking one of the items should return focus to the “Export” button.
In Harvest, we’ve gotten feedback that interactions with lists of time entries needed some changes that weren’t initially obvious. Specifically, after deleting an entry from a list with a button, focus should shift to the next or previous item. This is a relatively easy change to make, but it has a big impact on navigating Harvest via keyboard. In the following example, focus should shift to the Design or Programming row after removing the Marketing row. This is a case where it’s not entirely clear which row to shift focus to, but either choice is better than tabbing through the entire document to find your place again.
That’s a wrap
Another thing that we’re working on is providing focus wrapping in modal components like dialogs and menus. When you reach the end of the control, focus shouldn’t drop off to somewhere else on the page. Instead, it should wrap to the first focusable element within the component.
This can be difficult because, to my knowledge, there aren’t any standard ways to provide this focus wrapping. We’re currently using a custom selector that captures all the elements that are focusable in Harvest. This is a little fragile and very specific to Harvest, but it seems to be working so far.
At some point in the future, we hope to use the as-of-yet-under-supported inert attribute. Until then, our custom selector will have to do the trick!
Harvest contains many custom select elements that can filter a list of choices before selecting one. These are very handy, but were initially quite inaccessible. They used an anchor tag without an href as the main clickable element, and provided no aria attributes to inform the screen reader of the current selection. While they did the job for visual users, they were completely unusable for everyone else.
After a good deal of work, we provided a button as the main clickable element and added aria-owns and aria-active-descendant attributes to track the selected item. This has worked out well, but required a lot of work. If you can possibly use a standard control, I would recommend you do so. If not, make sure you fully research the accessibility of any custom controls you add before committing, and communicate the extra cost to the stakeholders in your project.
Harvest has come a long way recently, and we’ve written a bunch of controls that are easy to make accessible. That said, we’re not finished and I don’t think we’ll ever be finished. Improving the accessibility of our components and the user experience in general is a continual process that we’ll be maintaining going forward. In the meantime, I hope this helps give a sense of what this work actually entails.