Lessons from building mobile-friendly, accessible data tables

This is the fifth post in a series on accessibility from Shopify’s UX team. We’re publishing posts every two weeks. You can check out the introduction here.

Last December I joined the Reports team at Shopify to build a mobile-friendly solution for data tables. Reports provides our merchants with data that helps them make decisions about their store, and in most cases this data is presented in tables. For a while, these data tables were a big pain point for mobile users, as the small screens would squish the tables making the content illegible.

The designers on the Reports team came up with a slick solution for mobile data tables: sticking the leftmost column to the side, and have the rest of the table scroll beneath it. This allowed users to view the full table regardless of the number of columns.

Data tables: old (left) vs new (right)

I was still fairly new to accessibility when I started this project. But I learned a lot from a testing session with a visually-impaired user who performed a series of general tasks in the Shopify Admin. Specifically, I was interested in how he used the screen reader to navigate around the Admin and took note of the stumbling blocks he encountered.

One common problem I noticed was how the screen reader couldn’t navigate several elements in the Shopify Admin — even leading to some being omitted. Typically, the offending elements were non-semantic markup and other bad practices. A visually-impaired user should have access to the same information as any other user, and I wanted to make sure that that was the case with our data tables.

In this post I’ll share some stories and lessons on accessibility from my experience building a mobile-friendly data table. I left with four takeaways from this project that we’ll apply to future projects:

  1. Don’t become overly attached to your prototypes
  2. Don’t underestimate the effect of CSS on screen readers
  3. Sweat the small stuff
  4. Stick to semantic HTML

Lesson 1: Don’t become overly attached to your prototypes

Prototypes are important because they help convince project stakeholders why a particular approach is a good idea, and they demonstrate an idea in a more tangible way than a static composite. I started by creating a table with swipeable columns in Codepen, using the Flickity Javascript plugin and a markup that mimicked tables.

The prototype felt great, and for all intents and purposes it worked. Going forward with a similar implementation would have been tempting and easy, but the truth was that under the hood it was a mess. The scrolling effect was handled by the Javascript plugin, and the markup consisted of <div>s instead of a standard <table>.

Due to the non-semantic markup the table (if we could even call it that) was an absolute nightmare to navigate using a screen reader. It would not appear under the “Tables” menu in VoiceOver’s rotor menu, making it hard to discover. Furthermore, in order for the swiping to work the cells had to be wrapped by columns, as opposed to the more standard row-by-row approach. This made moving between table cells with the arrow keys incredibly frustrating as it prevented horizontal movement between columns.

Prototypes are really helpful in pushing a project forward, but they should really be no more than a proof of concept. You should always be ready to throw them away in favour of a more semantic, accessible approach.

Lesson 2: Don’t underestimate the effect of CSS on screen readers

Trying to achieve the fixed column using CSS unveiled many unexpected problems. I played around with absolute positioning, negative margins, transforms — you name it — and every approach seemed to lead to some sort of screen reader oddity.

For example, I learned that visually hiding columns could break VoiceOver’s handy keyboard commands. While implementing the fixed column’s CSS, I used the WebAIM-recommended “position content off-screen” technique to visually hide a column but still have it be readable by screen readers. This achieved the look that I was aiming for, but I noticed that it broke some useful keyboard commands. When I tried VoiceOver’s VO + C + C command to read the current column’s header, it would incorrectly read out the adjacent column’s header title. This could lead to a frustrating experience for screen reader users that rely on these type of commands to orient themselves in a table.

When the first column is visually hidden, VoiceOver’s “read column heading” command reads the incorrect heading (http://codepen.io/tetchi/pen/NAzXqL)

Another interesting discovery was that having CSS borders on table rows can affect whether or not the screen reader considers a table as a data table or a layout table. This could be problematic if you have a data table that you want to be listed in VoiceOver’s “Tables” menu but does not appear there because VoiceOver thinks it’s a table being used to lay elements out on a page. As it turns out, borders are one of many heuristics used to determine if a table is being used for data or layout and can lead to unexpected results.

For more on this topic, check out my blog post here.

Left: HTML table with no bordered rows. Right: HTML table with bordered rows

It’s important to regularly check the effects of CSS changes on the screen reader. Otherwise, you may unknowingly make navigating harder for screen reader users.

Lesson 3: Sweat the small stuff

This is not too far off from Lesson 2, but I’d like to reiterate the importance of testing. Screen reader testing should be part of your regular workflow, as it will help you spot unexpected issues and prevent potentially huge issues from being released out in the wild.

As an example, I was navigating a table in VoiceOver when I noticed something strange with how it handled negative numbers. When I selected a cell with a negative value (ex: “-$99.99”), it would omit the negative sign and read it as “dollar nine nine point nine nine.” It turns out that VoiceOver does not read hyphens as negative signs, which shocked me since we regularly use hyphens in code to denote negative values.

To remedy this, I replaced all hyphens with the Unicode character for a minus sign. Switching to the proper unicode character ensured that screen readers properly read negative values.

There are many examples of elements that appear to make sense visually but do not translate well on screen readers. As such it is extremely important to test on a regular basis to catch these little quirks.

Lesson 4: Stick to semantic HTML

When I was implementing the fixed column design, I realized that I was trying to fit a square peg in a round hole by twisting and turning a single HTML table. The solution that I ended up going with was to use two tables!

The idea was simple: have two identical tables stacked on top of the other using absolute positioning. The table on top would only have the leftmost column visible, so that the table below can scroll under it. It would also have the “aria-hidden” attribute set to true, making it invisible to screen readers. This way, I could play around with the styling of the fixed table as much as I’d like without affecting the semantics and accessibility of the scrollable table below it.

The fixed table only shows the first column, and sits on top of the scrollable table. It is also ignored by screen readers.

As Dave mentioned in his post, sticking to semantic HTML is really the best way to create accessible web experiences. Letting the top table handle all the visual styling and the bottom table be a normal table allowed me to use regular HTML that doesn’t require any CSS hacks and would be read by screen readers without any issues.

The tradeoff to this solution was that there would be extra markup due to the duplicate table, which does have performance implications on a page. However, I felt that it was a good compromise to achieve semantic HTML, accessibility, and mobile-friendliness in return.


I stumbled across many interesting accessibility-related issues and oddities while building this data table, but in the end I was really happy with how it turned out. The result is friendly for both mobile and screen reader users.

This project really cemented the idea that accessibility should be taken into consideration from the beginning of a project and not something that is shoehorned in after. Hopefully the lessons here can help you in your journey building accessible experiences!