HTML Formatterhtmlformatteronline.com

Responsive data table

A real table that stays readable at 390px, using a label-per-cell stack.

tableresponsivedatamobile
<table class="data">
  <caption>Orders placed in April 2026</caption>
  <thead>
    <tr>
      <th scope="col">Order</th>
      <th scope="col">Customer</th>
      <th scope="col">Placed</th>
      <th scope="col" class="num">Items</th>
      <th scope="col" class="num">Total</th>
      <th scope="col">Status</th>
    </tr>
  </thead>
  <tbody>
    <tr>
      <th scope="row" data-label="Order">#10482</th>
      <td data-label="Customer">Priya Raman</td>
      <td data-label="Placed"><time datetime="2026-04-02">2 Apr</time></td>
      <td data-label="Items" class="num">3</td>
      <td data-label="Total" class="num">£174.00</td>
      <td data-label="Status"><span class="pill ok">Shipped</span></td>
    </tr>
    <tr>
      <th scope="row" data-label="Order">#10483</th>
      <td data-label="Customer">Tomás Oliveira</td>
      <td data-label="Placed"><time datetime="2026-04-03">3 Apr</time></td>
      <td data-label="Items" class="num">1</td>
      <td data-label="Total" class="num">£58.00</td>
      <td data-label="Status"><span class="pill wait">Packing</span></td>
    </tr>
    <tr>
      <th scope="row" data-label="Order">#10484</th>
      <td data-label="Customer">Ada Nwosu</td>
      <td data-label="Placed"><time datetime="2026-04-05">5 Apr</time></td>
      <td data-label="Items" class="num">7</td>
      <td data-label="Total" class="num">£402.50</td>
      <td data-label="Status"><span class="pill ok">Shipped</span></td>
    </tr>
    <tr>
      <th scope="row" data-label="Order">#10485</th>
      <td data-label="Customer">Marek Bąk</td>
      <td data-label="Placed"><time datetime="2026-04-06">6 Apr</time></td>
      <td data-label="Items" class="num">2</td>
      <td data-label="Total" class="num">£92.00</td>
      <td data-label="Status"><span class="pill bad">Returned</span></td>
    </tr>
  </tbody>
  <tfoot>
    <tr>
      <th scope="row" colspan="3">Total</th>
      <td class="num">13</td>
      <td class="num">£726.50</td>
      <td></td>
    </tr>
  </tfoot>
</table>

How it works

The usual fix for a table on mobile is to wrap it in a horizontally scrolling box. That works, and it means nobody can see more than three columns at a time. This approach turns each row into a small labelled card instead.

The table stays a table. Real <table>, <thead>, <th scope> markup throughout. The mobile layout is a presentational change, so screen readers still get proper table semantics and can navigate by row and column.

data-label carries the column heading. On a narrow screen the header row is visually hidden and each cell prints its own label from attr(data-label). It is duplication, which is the cost of this pattern — generate it from the same source as your header row and it stays in step.

The header row is hidden, not removed. clip-path: inset(50%) rather than display: none, so it remains in the accessibility tree and screen readers can still associate cells with their columns.

scope on every header. scope="col" on the column headings and scope="row" on the first cell of each row. This is what lets a screen reader announce "Order, #10483" instead of reading a disconnected value.

tabular-nums. Most typefaces use proportional figures by default, so a 1 is narrower than a 0 and columns of numbers do not line up. One property fixes it and almost nobody uses it.

The first cell of each row is a th. The order number identifies the row, so it is a header, not data. That is the difference between a screen reader saying "row 3, #10484" and "row 3, column 1, #10484".

Accessibility notes

The <caption> names the table. It is the first thing a screen reader announces, and it is what tells someone whether this is the table they wanted before they commit to reading it.

Never use role="presentation" on a data table. That is for layout tables, and it strips exactly the semantics this table depends on.

Status is shown as a coloured pill with a word in it, not colour alone. Someone who cannot distinguish the green from the red still reads "Shipped" and "Returned".

Making it yours

The 680-pixel breakpoint depends on how many columns you have. Six columns need to break earlier than three; test by narrowing the preview until the headings start truncating.

For very wide tables — twenty columns of financial data — the stack becomes a very long card and scrolling is genuinely better. Wrap the table in a container with overflow-x: auto and add tabindex="0" so keyboard users can scroll it.

To keep the header visible while scrolling a long table, thead th { position: sticky; top: 0 } is one line and works with border-collapse: separate in current browsers.

Related templates

Check your version

Once you have edited this, the HTML validator will catch any tag you left unclosed, and the formatter will tidy the indentation. Both run in your browser.