The tab stop problem

The obvious way to build an editable grid is to render an `<input>` in every cell. It works with a mouse and it is a disaster with a keyboard: a 20-row, 8-column grid becomes 160 tab stops. Tabbing past the grid to reach the button underneath means 160 presses.

This is why editable grids are rare in free component libraries. Making one look right takes an afternoon. Making one that a keyboard user can navigate takes implementing a documented interaction model, and that is where most attempts stop.

Roving tabindex, the actual mechanism

The ARIA grid pattern says a composite widget has exactly one tab stop. Inside it, arrow keys move a notional focus between cells. The implementation is a roving tabindex: exactly one cell has `tabindex="0"` and every other has `tabindex="-1"`, and those values move as the user navigates.

So Tab enters the grid and Tab leaves it — two presses, not 160 — while arrow keys move within. This is the same model native `<select>` and radio groups use, which is why it feels familiar even to users who have never thought about it.

What the screen reader needs to hear

Moving to a cell should announce the column header, the row context and the value. That is what makes a grid navigable without sight: position alone is meaningless, because 'row 4, column 3' does not tell you what you are editing.

The grid also needs honest `aria-rowcount` and `aria-colcount`. If the grid virtualises rows, these must describe the full dataset rather than the rendered window, otherwise a screen reader announces a confidently wrong total.

The other components with the same problem

`Scheduler` has a harder version of it: a timeline communicates through position, and position is exactly what a screen reader cannot perceive. Every entry needs a text representation carrying the information the layout conveys visually. MUI's scheduler sits behind a paid licence and most free alternatives have no keyboard model at all.

`KanbanBoard` faces the starkest version. HTML5 drag-and-drop has no keyboard equivalent whatsoever — not a partial one, none. Supporting keyboard users means shipping a second complete interaction path and announcing moves through a live region. `FileTree` needs arrow keys that cross folder boundaries, `*` to expand a level and typeahead; most 'trees' are nested divs with click handlers.

Why this is the honest test of a component library

Buttons and modals are where libraries compete on looks. Grids, schedulers, Kanban boards and trees are where they compete on whether anyone did the difficult work. You can evaluate any library in about two minutes: open its most complex component and try to use it with the keyboard only.

If Tab traps you, if arrow keys do nothing, or if a drag-and-drop board cannot be operated at all without a mouse, you have your answer regardless of how the documentation reads. Apply that test to VivekUI too — it is the test the library was built to pass.

Key takeaways

  • An input per cell makes a 20x8 grid 160 tab stops; the ARIA grid pattern makes it one
  • Roving tabindex is the mechanism: one cell at tabindex 0, the rest at -1, moving with the user
  • Navigation mode and edit mode must be distinct or arrow keys cannot move a text caret
  • Announce column header, row context and value on move — position alone is meaningless without sight
  • aria-rowcount must describe the full dataset, not the virtualised window
  • Test any library by operating its most complex component with the keyboard alone

Conclusion

The keyboard model is the whole reason these components are rare. A grid that looks right is an afternoon's work; a grid that a keyboard user can actually operate is an implementation of a documented interaction model, and that difference is what separates a demo from a component you can ship.

Enjoyed this article?

Vivek Kumar Singh

Vivek Kumar Singh

Technical Expert · Full Stack Cloud Engineer · Tokyo, Japan