Tables — Real Data, and Making It Fit a Phone
IntermediateA table is the right element for tabular data and the wrong one for layout. Headers with scope make it navigable; a phone needs a deliberate strategy, not a horizontal scrollbar by accident.
Overview
Tables carry an old reputation from the era when they were used for page layout, and the reaction — building data grids out of divs — throws away real functionality. A properly marked-up table lets a screen reader announce "Difficulty, Easy" as it moves across a row, so a blind user knows which column they are in. Div grids announce a stream of disconnected values. The genuine difficulty with tables is not semantics, it is that eight columns do not fit on a 360px screen, which needs a decision rather than an accident.
The Full Markup
caption, thead, th and scope — each one does a specific job for a screen reader.
<table>
<caption>Submissions this week</caption>
<thead>
<tr>
<th scope="col">Problem</th>
<th scope="col">Difficulty</th>
<th scope="col">Runtime</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Two Sum</th> <!-- the row's own header -->
<td>Easy</td>
<td>42 ms</td>
</tr>
</tbody>
<tfoot><tr><td colspan="3">3 of 12 solved</td></tr></tfoot>
</table>
// scope="col" and scope="row" are what let a screen reader say
// "Two Sum, Difficulty, Easy" instead of just "Easy".
// caption is the table's accessible name, announced on entry.
// It also survives when the surrounding heading does not.
// Numbers align right and use tabular figures so columns line up:
td.num { text-align: right; font-variant-numeric: tabular-nums }Tables on a Phone
Three strategies. Pick one deliberately; the default is none of them.
/* 1. Scroll horizontally — honest, keeps the table intact.
Needs a focusable, labelled container or keyboard users
cannot scroll it. */
<div class="table-wrap" tabindex="0" role="region" aria-label="Submissions">
<table>…</table>
</div>
.table-wrap { overflow-x: auto; -webkit-overflow-scrolling: touch }
/* 2. Stack into cards below a breakpoint — best for a few rows */
@media (max-width: 640px) {
thead { display: none }
tr { display: block; border: 1px solid var(--rule); margin-block: .5rem }
td { display: flex; justify-content: space-between }
td::before { content: attr(data-label); font-weight: 600 }
}
<td data-label="Difficulty">Easy</td>
/* 3. Hide low-priority columns, with a way to reveal them */
@media (max-width: 640px) { .col-runtime, .col-memory { display: none } }
/* Sticky header for a long table */
thead th { position: sticky; top: 0; background: var(--surface) }When Not to Use a Table
And what to reach for instead.
// Not a table: page layout. Use grid or flexbox.
// Not a table: a list of cards. Use a ul.
// Not a table: a form's field alignment. Use grid.
// A table is right when the data has rows AND columns whose
// intersection means something. If you would export it to CSV,
// it is a table.
// If you must build a grid from divs — for virtualisation, say —
// you have to add every role by hand, and get them all right:
<div role="table" aria-rowcount="500">
<div role="rowgroup">
<div role="row"><span role="columnheader">Problem</span></div>
</div>
<div role="row"><span role="cell">Two Sum</span></div>
</div>
// This is a genuine reason to use a headless table library rather
// than reinventing it.
// Sortable columns must announce their state:
<th scope="col" aria-sort="ascending">
<button>Runtime</button>
</th>Key Points to Remember
- 1scope="col" and scope="row" let a screen reader announce the header with each cell
- 2caption is the table's accessible name and is announced when a user enters it
- 3A horizontally scrolling table wrapper needs tabindex and a label, or keyboard users cannot scroll it
- 4Stacking rows into cards with data-label pseudo-content is the usual small-screen alternative
- 5Use a table when rows and columns intersect meaningfully; a div grid means writing every ARIA role by hand
Interview Questions
Sign in to ask AriaWhat does the scope attribute do on a table header?
How would you make an eight-column table usable on a 360px screen?
When is a <table> the wrong element?
Ask Aria about Tables — Real Data, and Making It Fit a Phone
Your personal AI tutor — ask anything about this concept
Revision Status
Personal Notes
Sign in to save personal notes for this topic.
Discussion
Sign in to join the discussion.