Responsive Tables on Small Screens

A table with eight columns cannot fit on a phone. Four patterns that keep the data usable, and the trade off each one makes.

Concept

Tables are the hardest thing on the web to make responsive, because a table is a grid and a phone screen is a column. Every solution is a compromise; the skill is choosing which compromise fits the data.

Pattern 1: horizontal scroll

The simplest and the safest. The table keeps its structure and the reader scrolls sideways.

<div class="table-scroll" tabindex="0" role="region" aria-labelledby="fees-caption">
  <table>
    <caption id="fees-caption">Fee structure by course and year</caption>
    ...
  </table>
</div>
.table-scroll {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
}
.table-scroll table {
  min-width: 40rem;      /* below this the columns are unreadable anyway */
  border-collapse: collapse;
}

Three details make it work properly. tabindex="0" lets a keyboard user reach and scroll the container. role="region" with a label means a screen reader announces what is being scrolled. And a shadow at the edge tells the reader there is more:

.table-scroll {
  background:
    linear-gradient(to right, #fff 30%, rgba(255,255,255,0)),
    linear-gradient(to left,  #fff 30%, rgba(255,255,255,0)) 100% 0,
    radial-gradient(farthest-side at 0 50%, rgba(15,23,42,.15), transparent),
    radial-gradient(farthest-side at 100% 50%, rgba(15,23,42,.15), transparent) 100% 0;
  background-repeat: no-repeat;
  background-size: 40px 100%, 40px 100%, 14px 100%, 14px 100%;
  background-attachment: local, local, scroll, scroll;
}

Trade off: none structurally. The reader has to scroll, which is honest.

Pattern 2: stacked cards

Below a breakpoint each row becomes a block, with the column name printed beside each value using a data attribute.

<tbody>
  <tr>
    <th scope="row" data-label="Route">12A</th>
    <td data-label="From">Station Road</td>
    <td data-label="First bus">06:10</td>
    <td data-label="Frequency">Every 20 minutes</td>
  </tr>
</tbody>
@media (max-width: 40rem) {
  table, thead, tbody, tr, th, td { display: block; }

  thead { position: absolute; left: -10000px; }   /* off screen, still announced */

  tr {
    border: 1px solid #cbd5e1;
    border-radius: 8px;
    margin-bottom: 1rem;
    padding: 0.5rem;
  }

  td, th {
    display: grid;
    grid-template-columns: 9rem 1fr;
    gap: 0.75rem;
    padding: 0.35rem 0;
    text-align: left;
  }

  td::before, th::before {
    content: attr(data-label);
    font-weight: 600;
    color: #475569;
  }
}

Trade off: a real one. Setting display: block on table elements strips their table semantics in most browsers, so a screen reader no longer announces headers with cells. The data-label text is generated content and is announced inconsistently. Use this pattern for short, simple tables where losing the grid semantics does not lose the meaning - and duplicate the label in real text if the data is important.

Pattern 3: hide low priority columns

<th scope="col" class="col-optional">Room</th>
...
<td class="col-optional">B12</td>
@media (max-width: 40rem) {
  .col-optional { display: none; }
}

Straightforward and often right - a timetable on a phone rarely needs the room number in the same glance. Pair it with a link to a full view.

Trade off: data disappears. Only hide what is genuinely secondary, and never hide it silently on a page where the reader is comparing values.

Pattern 4: pivot the table

Swap the axes so each record becomes a column. Useful for comparison tables with few records and many attributes - three plans compared across twelve features, for instance.

Trade off: only works with a small number of records, and usually means maintaining two markup variants.

Choosing

SituationPattern
Many columns, data must stay comparableHorizontal scroll
Few columns, each row read on its ownStacked cards
Some columns clearly secondaryHide columns
Few records, many attributesPivot
Long numeric table for analysisScroll, plus a download link

What helps in every pattern

  • A sticky first column, so the row label stays visible while scrolling sideways.
  • font-variant-numeric: tabular-nums so digits align.
  • Right aligned numbers, left aligned text.
  • Generous cell padding; a cramped table is unreadable at any width.
  • A visible caption that survives every breakpoint.
tbody th[scope="row"] {
  position: sticky;
  left: 0;
  background: #ffffff;
}
td.numeric {
  text-align: right;
  font-variant-numeric: tabular-nums;
}

Important rules

  • Changing display on table elements removes table semantics. That is the cost of the stacked pattern.
  • A scroll container must be keyboard reachable and labelled.
  • Never rely on generated content alone to carry data.
  • Set min-width on a scrolling table, or it will squeeze itself into unreadable columns instead of scrolling.
  • Test at three hundred and twenty pixels wide, the narrowest screen still in common use.

Common mistakes

  • Letting a table overflow the page so the whole document scrolls sideways.
  • Using the stacked pattern on a large data table and destroying its semantics.
  • Hiding columns that the reader actually needs.
  • A scroll container with no keyboard access.
  • Shrinking font size until the table fits, producing text nobody can read.
  • Testing only in a resized desktop window rather than on a real device.

Best practices

  • Start with horizontal scroll. It is the safest default and needs no compromise.
  • Reach for stacked cards only when the table is small and simple.
  • Add a sticky row header for wide tables.
  • Offer a CSV or PDF download for large datasets.
  • Ask whether the data needs to be a table on a phone at all - a chart or a summary may serve better.

Practice

  1. Wrap a wide table in a labelled, focusable scroll container and confirm you can scroll it with the keyboard alone.
  2. Build the stacked card pattern for a four column table, then test it with a screen reader and describe what is lost.
  3. Make the first column sticky and scroll a ten column table sideways.
  4. Test a table at three hundred and twenty pixels and list every problem you find.

Useful resources

Hand picked references for this topic
Written by Lorens Mishra

Default administrator account created by the installer.

Continue reading

All HTML notes →

Discussion

0 comments
Sign in to join the discussion.

No comments yet. Be the first to say something.