How to make an HTML table responsive

There are three real patterns and the choice depends on how the table is read. Grids scroll, lists stack, and wide reports drop columns. Picking the wrong one makes the table harder to use than it was.

To make an HTML table responsive, pick one of three patterns: scroll it inside a wrapper, stack each row into a block, or hide the secondary columns below a breakpoint.

There is no fourth option worth using, and the choice is decided by how people read the table, not by how wide it is.

A six column table at phone width with no responsive handling. Every heading has wrapped onto four lines.
A six column table at phone width with no responsive handling. Every heading has wrapped onto four lines.

Which pattern fits which table

Pattern Fits Breaks
Scroll sideways Grids, figures compared across rows and columns Nothing, but the reader must discover the scroll
Stack into blocks Lists where each row is one item Any comparison down a column
Hide columns Wide reports with a clear primary and secondary split Anything the reader needs to act on

The middle row is the one applied too often. A monthly figures table turned into a stack of cards is no longer a table, and the whole reason someone opened it was to compare February against March.

Pattern one: scroll

The default answer, and one line of CSS.

<div class="table-scroll" tabindex="0" role="region" aria-label="Monthly figures">
  <table>...</table>
</div>
.table-scroll { overflow-x: auto; max-width: 100%; }
table { min-width: 640px; }

The wrapper scrolls, not the table, because a table element does not honour overflow. The min-width gives the table a reason to be wider than the box, otherwise it shrinks to fit and the columns crush.

Add a sticky first column and the pattern becomes much better on a phone, because the row labels stay in place while the numbers slide past.

Overflow scroll covers the details, including how to signal that the table scrolls at all.

The same table at phone width inside a scrolling wrapper. Columns are at full width and the first column is pinned.
The same table at phone width inside a scrolling wrapper. Columns are at full width and the first column is pinned.

Pattern two: stack

For a table that is really a list, each row can become a small block below a breakpoint.

@media (max-width: 600px) {
  thead { display: none; }
  tr { display: block; margin-bottom: 1rem; border: 1px solid #e3e3e3; }
  td { display: block; text-align: right; }
  td::before {
    content: attr(data-label);
    float: left;
    font-weight: 600;
  }
}

Each cell needs data-label="Status" in the markup, because the header row is gone and the value would otherwise be unlabelled.

Two costs to know about. The labels are duplicated into every row, so the markup grows. And display: block on table elements removes their table semantics, which changes what a screen reader announces.

The second cost is the serious one. If the table carries anything a reader depends on, test it with a screen reader before shipping the stack.

Pattern three: hide columns

Wide reports often have two or three columns that carry the answer and several that support it.

@media (max-width: 720px) {
  .col-secondary { display: none; }
}

Put the class on the <th> and the matching <td> in every row, or use nth-child if the positions are stable. Keep the row identifier and the primary figures.

Say somewhere that the table is fuller on a wider screen. A silently truncated table looks like missing data.

The settings that apply to all three

Three things are worth doing whichever pattern you pick.

  • The viewport tag. Without <meta name="viewport" content="width=device-width,initial-scale=1"> the phone renders a desktop width page and zooms out, and none of the media queries fire. See the viewport meta tag.
  • Border box sizing. Padding counts inside the declared width, so column widths mean what they say. See box sizing.
  • Sensible column widths. A <colgroup> with percentages adapts on its own. See column width.

What none of the three patterns fixes

A table with eleven columns is hard to read on a laptop too. Responsive CSS moves the problem to a smaller screen rather than solving it.

Before reaching for a media query, check whether the table is doing two jobs at once. A summary table with five columns and a detail table behind a link is often better than one table with sixteen.

The same applies to precision. Figures given to four decimal places widen every column for information almost nobody reads at that resolution.

A wide table next to the same data as a five column summary with a link to the detail.
A wide table next to the same data as a five column summary with a link to the detail.

Testing it properly

Resizing the browser window gets you most of the way. Three checks decide whether it is done.

  1. At 360 pixels wide, does the page scroll sideways? It should not. Only the table wrapper should.
  2. Can you reach every value? Scroll the table to the far right and confirm the last column is reachable by touch and by keyboard.
  3. Does the header still identify the columns? After scrolling, either the header is pinned or the first column is, or the reader is guessing.
A list table below the breakpoint, each row rendered as a labelled block.
A list table below the breakpoint, each row rendered as a labelled block.

Open the file in the HTML file opener and narrow the window there, so nothing from your own project stylesheet props it up.

Getting the responsive table to readers

A responsive table is only responsive if the reader opens the real page. A screenshot or a PDF export fixes one layout, usually the desktop one, which is the layout least likely to be the one they need.

Paste the HTML into a NOS document. It renders as written, media queries included, at an address of its own. Share, then Share link, then Create link produces an unlisted link that opens in one click on a phone.

The address survives edits, so a corrected number reaches everyone who already has the link, with nothing resent. HTML to link is the general version of that route.

Questions people ask

What is the simplest way to make a table responsive?

Wrap it in a div with overflow-x: auto. The columns keep their real widths and the table scrolls sideways inside the page instead of stretching it. That is one line of CSS and it works for most data tables.

Should a table stack into cards on mobile?

Only if the table is read one row at a time, such as a list of orders where each row is a separate thing. If readers compare a column down the page, stacking destroys the comparison and scrolling is the better answer.

Is it acceptable to hide columns on small screens?

Yes, for columns that support the main figures rather than carry them. Hide them with a media query and keep the row identifier and the primary values. Do not hide anything the reader needs to act, and say somewhere that the full table is on a wider screen.

Why does my responsive table still stretch the page?

The overflow is probably on the table rather than on a wrapper around it, and a table element does not honour it. Check also that no ancestor has a width larger than the viewport, because the page scrollbar can come from something else entirely.

Keep reading