Hierarchy in tables

Over the last five years working at an accounting and HR software company, I’ve seen a lot of tables that should be lists, but there are still a lot of tables that absolutely should be tables.

Like most older (and many newer) software companies, accessibility was never really a consideration, so the way tables were put together was often very dense, with lots of hierarchy and clickable elements. Although they’re linked, those things are better tackled individually, so I’ll start with hierarchy.

The rules of play

There are a few things we need to be careful of when introducing parent-child relationships into a table of data. Visually, most of the hierarchical tables I’ve seen make at least some sense and, where they don’t, the ‘sell’ on making them understandable is more straightforward as it’s more of a general user experience problem which is easier to get sign off to tackle. So we’re talking about beyond the visual here; specifically screen reader users.

Info and relationships

The first pitfall is covered by the Web Content Accessibility Guidelines (WCAG), so it’s relatively easy to enforce. We need to ensure the information about hierarchical information is conveyed programmatically in order to avoid a failure of 1.3.1 Info and Relationships.

Cognitive load

The other consideration is more difficult to define as it is more about the user experience of screen reader users. If more than a simple table of data is being presented, people using screen readers have more information to keep in working memory in order to understand their position in the hierarchy. It’s our job not to overface our users.

An enforced constraint

My first instinct is to simplify, so I’d always explore ways to remove hierarchy from a table altogether, breaking the data down somehow, whether:

Multiple tables on the same page, separated by headings; the headings would provide the information on hierarchy

Multiple tables on separate pages, with a link on parent rows to ‘drill down’ into the next level of the hierarchy

But let’s pretend we can’t do that, and the hierarchical data must be displayed in a single table.

Conveying information about hierarchy

If I’m looking at a table that contains hierarchy, I should be able to distinguish the child content, so I’d maybe expect it to be indented; a bit like a nested list. I might even highlight the parent content somehow (bold text?), but that’s probably less important (remember, our parent rows aren’t clickable – that’s a separate article for another day).

The challenge is how to convey information about a table’s hierarchy programmatically. Focusing on the child content, I want the same kind of information that’s there visually via the indent to be communicated to screen reader users. So If I land on the indented first column in a child row, I’d want to hear something like:

Contents of current table cell

‘Joining’ text (something like “child of” or “belonging to”)

Name of parent cell, to give context

The reverse would also work:

Name of parent cell

Joining text

Contents of current table cell

ARIA

It’s not the first thing you should reach for, but ARIA was where my mind went to and (spoiler alert!) none of the approaches work reliably across screen reader software, so I thought I’d get it out of the way.

aria-labelledby

The spec doesn’t prohibit aria-labelledby use on a so I started there. Here’s an example of a leaderboard where both the teams’ scores and the team members’ scores are logged:

belonging to

Pokémon league


Name
Battles
Won
Lost
Points



Team Rocket
24
0
24
0


Jessie
8
0
8
0



aria-labelledby allows you to refer to the id of the element you’re on, so by combining the current cell, the joiner text, and the parent cell I’d expect it to read something like this:

Row 3 of 12 Name, Jessie belonging to Team Rocket, Column 1 of 5

And it does. But only on VoiceOver and JAWS; not on NVDA. So it’s a no-go.

Note: I only tested with these three screen readers as they gave me all the info I needed to make a decision.

aria-label

I then tried the same sort of labelling with aria-label instead:

Jessie

Exactly the same results: fine on VoiceOver and JAWS but no dice with NVDA.

aria-describedby

aria-describedby wouldn’t change the label, but it would, at least in theory, add the information that you’re hearing the data of a child element. So here’s the code:

Jessie

There was a problem with VoiceOver as it didn’t read the hidden joining text before the table, like it did with the aria-labelledby approach, but there was an even bigger problem with JAWS and NVDA as it didn’t work at all!

HTML

ARIA wasn’t the answer, so perhaps HTML would be?

headers attribute

The headers attribute seemed like cleanest way to do this as, similar to the aria-labelledby method:

Jessie

It almost works in VoiceOver but it doesn’t read the hidden joining text, probably because it’s hidden from assistive technology (although that didn’t stop aria-labelledby), or perhaps because headers can only reference elements. Either way, there’s a gap between the first and third references which doesn’t communicate the hierarchy clearly enough:

Row 3 of 12 Name, and Team Rocket, Jessie, Column 1 of 5

But that’s the least of our worries! As with aria-labelledby, it doesn’t work at all in either JAWS or NVDA. In the bin.

Visually hidden text

So far no dice, so there’s only one thing for it! Clunky and inelegant but reliable visually hidden text. So this would be the HTML:

Jessie belonging to Team Rocket

And the CSS we’re calling would look like this:

.visually-hidden {
clip-path: inset(50%);
height: 1px;
overflow: hidden;
position: absolute;
white-space: nowrap;
width: 1px;
}

As expected, this worked reliably across all the screen readers I tested with.

Keeping cognitive load light

We have our technique, but the real problem with all of this is cognitive load. A table with hierarchy can relatively easy to understand visually, but non-visually there’s a lot of information from the markup to keep in RAM. The user is there to look up or compare data, and that’s where their energy should be spent; not on working out where in the table they are and how many levels deep.

That brings up another issue: how many levels of hierarchy are too many? There’s no hard and fast rule as it very much depends on the content:

Number of columns

Number of row

Complexity of data

Variety of data types

Introducing any hierarchy at all already feels like it could compromise screen reader users’ experience, but anything other than simple data is going to start feeling overwhelming.

If a complex hierarchical table can be broken down into several more digestible tables, that’s what should be done. If it’s absolutely unavoidable to use hierarchy in a table, it should be done with caution.