Building Accessible Grids 1
Tags: a11y, webdev, html, react
Published: 2020-06-20 21:09:35

---
title: Building Accessible Grids 1
published: true
description: Adding grid-like keyboard navigation to UI components
tags: a11y, webdev, html, react
cover_image: https://dev-to-uploads.s3.amazonaws.com/i/inqvc3irmmu7j5pyzmeg.png
---

- **Interactive Element**: Any html element that can receive focus. For example the `a`, `button` and `input` elements are interactive by default. Any element can be made interactive by adding a `tabindex="0` attribute to it. Similarly, any element can be made non-interactive by using `tabindex="-1"`.


Part 2: {% link https://dev.to/tryggvigy/building-accessible-grids-2-34ha %} 

---

## Background

Building accessible lists and grids on the web is unfortunately difficult.

In frameworks like React, where a Grid component does not necessarily know about or control its children, it can be even harder.

Using a `<table>` and associated child elements is the right choice sometimes. Other times it's not. These elements are awkward to lay out and more importantly impose limitations on the DOM structure . More complex grids may need to use `<div>`s.

- Grids should be navigable with arrow keys. You can read about the expected keyboard support for data grids in the [WAI ARIA practices](https://www.w3.org/TR/wai-aria-practices/examples/grid/dataGrids.html).
- Grids should be skippable with a single Tab key press. They are _composite components_. You can read more in this excellent thread {% twitter 1274405491238531072 %}


One way to add keyboard support to grids is to base the navigation on the current state of the DOM instead of the framework state (React and Vue for example).

## Aria attribute DOM based way

```html
<div role="grid">
  ...
  <div role="presentation">
    ...
    <div role="row" aria-rowindex="1">
      ...
      <div role="presentation">
        ...
        <div role="gridcell" aria-colindex="1">
          ...
          <a tabindex="-1"></a>
          <button tabindex="-1"></button>
        </div>
      </div>
    </div>
  </div>
</div>
```

Let's walk through how this is done on a high level. In the next post we'll go more concrete and actually implement this.

- Create the root grid element
  - Give grid the `role="grid"` attribute. 
  - Give grid `tab-index="0"` so users can navigate to it (remember to add some CSS focus styles. E.g. a border or an outline that indicates the grid is in focus)
- Add rows to the grid
  - Give row `role="row"`
  - Give row the correct `aria-rowindex` (starts with 1)
- Add cells (a row,col combination) to the rows
  - give cell either a `role="columnheader"` if it's a column header, or a `role="gridcell"` if it's a regular cell
  - Give cell the correct `aria-colindex` attribute (starts with 1)
- Make sure all elements between `grid` -> `row` -> `gridcell` that do not have an explicit role, get `role="presentation"` added to them.
  - This removes those elements from the accessibility tree and ensures screen readers can correctly interpret and navigate the grid.
- Make sure that **all** interactive elements in the grid have `tabindex="-1"` attribute
  - This removes them from the natural tab order of the page and ensures that users can jump over the whole grid with a single Tab key press if they want to skip it.
- Add a keydown listener to the root grid element (`role="grid"`) and respond to key presses like the arrow keys.
- Once an arrow key is pressed we choose what to focus next in the DOM. More on that in a bit.

The DOM based approach gives us the benefit of the focus management being transparent to the grid and its child components. If a new interactive element is added to a cell in the grid it will work without modification.

The downside is frameworks like React can change the DOM at any time. Because of this, the first thing that is done in keydown event handler before moving any focus is ensuring that a focusable element (with `tabindex="0"` attribute) actually exists in the grid. If it doesn't exist, fall back to the first interactive element in the grid. 

This should not happen much unless your framework is adding/removing cells from the DOM frequently. I have not seen problems with this in a virtually scrolled, sortable/filterable grid that loads more data on scroll so I expect this is will work well in most cases.

### Roving tabindex 
Now that we have found the currently focused element in the grid we need to decide which element to focus next. If we find that element we move the focus to that element:
- `tabindex="0"` -> `tabindex="-1"` on the currently focused element
- `tabindex="-1"` -> `tabindex="0"` on the next element to focus
- call [`focus()`](https://developer.mozilla.org/en-US/docs/Web/API/HTMLOrForeignElement/focus) on the next element.

This technique is called _roving tabindex_. You can read more about it in the [WAI-ARIA Authoring Practices](https://www.w3.org/TR/wai-aria-practices-1.1/#kbd_roving_tabindex) or check out [this excellent video](https://www.youtube.com/watch?v=uCIC2LNt0bk) from the A11ycasts series on the Google Chrome Developers YouTube channel.

## Moving focus to the next element

So how do we find the next element to focus in a reliable way? This depends on what the grid actually is. If the grid is a spreadsheet it might make sense to focus the next cell element. But if the grid is a collection of links and buttons structured in a table - for example a playlist on Spotify, then jumping to the next interactive element would make sense.

In the next post I'll go through a concrete implementation keyboard support of a grid containing `a` and `button` elements.

## Wrapping up

As long as

- This hierarchy of selectors is in place
```css
[role="grid"]
└── [aria-rowindex]
    └── [aria-colindex]
        └── a, button
```
- `aria-rowindex`/`aria-colindex` are 1-based and sequential

Then the keyboard navigation will work. Additional wrapper elements between each layer do not matter.

Building accessible grids and lists can be daunting and too many don't bother. Fortunately adding keyboard support can be done in a generic, framework and use-case agnostic, repeatable way that is likely to survive future changes to the components without breaking or needing modification.

In the next post we'll implement a grid with proper keyboard support using this method.

Part 2: {% link https://dev.to/tryggvigy/building-accessible-grids-2-34ha %} 

---