Listbox Versus Grid for Selectable Record Lists

Permalink to "Listbox Versus Grid for Selectable Record Lists"

Many data interfaces have a list of records on the left and a detail pane on the right: an inbox, a queue of tickets, a list of customers. Each record shows a few fields — sender, subject, date — and users pick one to open. That list can be built as role="listbox" (each record is an option) or as role="grid" (each record is a row of cells). Both are correct for some lists and wrong for others, and the choice changes what users hear on every arrow press.

This page compares them and gives a rule for choosing. It belongs to composite widget roles & states.

Spec reference

Permalink to "Spec reference"

listbox contains option elements. One tab stop; Up/Down Arrow move between options; aria-selected marks selection; aria-multiselectable allows several. Each option is read as a single string — its accessible name — plus “selected” and position (“3 of 40”). Options may not contain interactive descendants.

grid contains row elements containing gridcell elements. One tab stop; arrows move in two dimensions; rows or cells can be selectable. Each move reads the focused cell (or row, with row focus) with its column header.

Criteria: SC 4.1.2 Name, Role, Value — the option’s name is the only thing a listbox user hears, so it must summarise the record; SC 1.3.1 — in a grid, the column relationship is programmatic; SC 2.1.1 Keyboard.

The same record, two roles Comparison of how one inbox record is announced and navigated as a listbox option versus as a grid row. The same record, two roles✓ listbox option"Northwind, Invoice overdue, 4 March, 3 of 40,selected"Up and Down onlyWhole record read as one phraseNo interactive content inside options✓ grid rowUp, Down, Left, RightFields read one at a time with headersCells may hold buttons or checkboxes
The listbox reads a sentence; the grid reads a field — choose by which one users need.

When to use each

Permalink to "When to use each"

Listbox when a record is something you pick: its fields are a description, users scan by hearing the whole thing, and the only action is “select this one”. Master–detail lists, pickers in dialogs, recipient suggestions. Keep the visible fields to three or four so the spoken name stays short.

Grid when users need to compare fields across records or act on individual fields: jumping down the Date column to find a range, toggling a star in one column, a per-row checkbox plus a per-row menu. Columns need headers, and the grid needs the full two-dimensional keyboard model.

Neither when the list is really a table that users read: use a native table with a link per row. Selection is then a checkbox column, and opening a record is following its link.

The misapplication to name is a listbox whose options contain buttons (star, archive, menu). ARIA forbids interactive descendants in options, screen readers cannot reach them, and keyboard users cannot activate them without a mouse.

Annotated code example

Permalink to "Annotated code example"
<!-- LISTBOX: each option is named by a summary of the record -->
<div role="listbox" aria-label="Inbox" tabindex="0"
     aria-activedescendant="msg-2">                 <!-- focus stays on the listbox -->
  <div role="option" id="msg-1" aria-selected="false">
    <!-- SC 4.1.2: visible fields make up the accessible name -->
    <span class="from">Contoso</span>
    <span class="subject">Weekly report</span>
    <time datetime="2026-03-03">3 March</time>
  </div>
  <div role="option" id="msg-2" aria-selected="true">
    <span class="from">Northwind</span>
    <span class="subject">Invoice overdue</span>
    <time datetime="2026-03-04">4 March</time>
  </div>
</div>

<!-- GRID: fields as cells with column headers, actions allowed in cells -->
<div role="grid" aria-label="Inbox" aria-multiselectable="true">
  <div role="row">
    <div role="columnheader">From</div><div role="columnheader">Subject</div>
    <div role="columnheader">Date</div><div role="columnheader">Star</div>
  </div>
  <div role="row" aria-selected="true">
    <div role="gridcell" tabindex="0">Northwind</div>
    <div role="gridcell" tabindex="-1">Invoice overdue</div>
    <div role="gridcell" tabindex="-1">4 March</div>
    <div role="gridcell"><button tabindex="-1" aria-pressed="false">Star</button></div>
  </div>
</div>
// Listbox: single-select, selection follows focus (common for master–detail)
listbox.addEventListener('keydown', (e) => {
  const opts = [...listbox.querySelectorAll('[role="option"]')];
  let i = opts.findIndex((o) => o.id === listbox.getAttribute('aria-activedescendant'));
  if (e.key === 'ArrowDown') i = Math.min(i + 1, opts.length - 1);
  else if (e.key === 'ArrowUp') i = Math.max(i - 1, 0);
  else if (e.key === 'Home') i = 0;
  else if (e.key === 'End') i = opts.length - 1;
  else return;
  e.preventDefault();
  opts.forEach((o) => o.setAttribute('aria-selected', 'false'));
  opts[i].setAttribute('aria-selected', 'true');
  listbox.setAttribute('aria-activedescendant', opts[i].id);
  opts[i].scrollIntoView({ block: 'nearest' });
  showDetail(opts[i].id);                           // detail pane updates, focus stays
});

Keyboard & AT behaviour

Permalink to "Keyboard & AT behaviour"
Key Listbox Grid
Down Arrow Next record, read in full: “Northwind, Invoice overdue, 4 March, 2 of 40” Same column, next row: “Contoso” (header announced on column change only)
Right Arrow Nothing (or next option in horizontal listboxes) Next field: “Subject, Weekly report”
Space Toggle selection (multi-select) Toggle row selection
Enter Open the record Activate the cell’s control or open the record
Type-ahead Jumps to option starting with typed text (if implemented) Rarely implemented
Listbox, grid or table? Decision tree for a selectable record list based on whether users pick whole records, work with individual fields, or mostly read. Listbox, grid or table?What do users do with each record?Pick itlistboxwhole record read as one optionWork with its fieldsgridcells, headers, actions per cellRead and followtable + linksbrowse mode keeps working
The unit users think in — record, field, or text — decides the role.

Integration context

Permalink to "Integration context"

Both roles can manage focus with aria-activedescendant (focus stays on the container) or roving tabindex (focus moves to each option or cell); the trade-offs are in using aria-activedescendant for grid cell focus.

For multi-select grids, the selection model and its announcements are in aria-selected on cells versus rows. The broader table-versus-grid question is in choosing between grid and table roles.

Words heard per arrow press Bar chart of words spoken per arrow press when scanning a record list as a listbox with three fields, a listbox with seven fields, and a grid moving down one column. Words heard per arrow pressListbox, 3 fields9 wordsListbox, 7 fields24 words — too long to scanGrid, down one column2 words
Listboxes read the whole record every time — keep option names short or use a grid.

Gotchas

Permalink to "Gotchas"

Long option names. Seven visible fields make a 25-word option name read on every arrow press. Trim the name with aria-label to the fields that identify the record, or switch to a grid.

Selection following focus in multi-select listboxes. Only follow focus in single-select lists; in multi-select, arrows must move without changing selection.

Virtualised listboxes. Options not rendered cannot be aria-activedescendant targets. Set aria-setsize and aria-posinset on rendered options so position is honest — see aria-setsize and aria-posinset in virtual lists.

Testing checklist

Permalink to "Testing checklist"

FAQ

Permalink to "FAQ"
Should an inbox-style list be a listbox or a grid?

A listbox when users pick one message and hear it as a single phrase; a grid when they need to move between fields such as sender and date, or act on individual controls like a star in each row.

Can listbox options contain buttons?

No. ARIA does not allow interactive descendants in options, and screen readers cannot reach them. If each record needs its own controls, use a grid, or a table with a link and buttons per row.

Should selection follow focus in a listbox?

In a single-select listbox used for master–detail, it usually should, so the detail pane follows the user. In a multi-select listbox it must not, or moving would clear the selection.

How long can a listbox option's name be?

Keep it short — the identifying fields, ideally under a dozen words — because it is read in full on every arrow press. If users need more fields, a grid lets them read one at a time.

Permalink to "Related"

← Back to Composite Widget Roles & States