A sortable HTML table that reads on a phone

Tables get shared worse than anything else. Pasted into a message the columns collapse; captured as an image they stop being data.

A sortable HTML table takes a few lines and fixes the most common complaint about shared tables, which is that you cannot reorder them.

A sortable HTML table that reads on a phone: real table markup, tabular numbers, a sort arrow in each header.
A sortable HTML table that reads on a phone: real table markup, tabular numbers, a sort arrow in each header.

Tables get shared worse than anything else: pasted into a message the columns collapse, sent as a spreadsheet they need an application, captured as an image they stop being data.

This guide covers designing the table for a phone first, the typography that makes it readable, sortable headers in a few lines, real table markup, the limit of editable cells in a file, and the four steps to put it at one address.

Tables get shared worse than anything else. Pasted into a message the columns collapse; sent as a spreadsheet they need an application; captured as an image they stop being data.

A sortable HTML table that reads on a phone

A table with six columns cannot be squashed into a phone screen and stay readable. Do not try — let it scroll:

<style>
  .scroller { overflow-x: auto; -webkit-overflow-scrolling: touch; }
  table { min-width: 560px; border-collapse: collapse; }
</style>

<div class="scroller">
  <table>...</table>
</div>

The wrapper scrolls; the rest of the page stays put. Two lines, and the table is usable on a phone instead of being a grid of truncated words.

The typography that makes a table readable

Choice Rule What goes wrong without it
Numbers text-align: right and font-variant-numeric: tabular-nums Magnitudes cannot be compared at a glance
Row separation A hairline bottom border only Vertical rules add noise, not information
Header row Tinted background, smaller uppercase text The eye cannot find the top of the table
Long cells vertical-align: top Mid-aligned text in a tall row looks like a fault
Column widths white-space: nowrap on short ones Dates and statuses wrap into two lines

The numeric one is the biggest single improvement and the most often skipped. Tabular figures give every digit the same width, so a column of numbers forms a straight edge and a reader can see which is largest without reading any of them.

Sortable headers

<script>
  var t = document.getElementById('t'), dir = {};
  [].forEach.call(t.tHead.rows[0].cells, function (th, i) {
    th.onclick = function () {
      var up = !dir[i]; dir = {}; dir[i] = up;
      th.setAttribute('aria-sort', up ? 'ascending' : 'descending');
      var body = t.tBodies[0], rows = [].slice.call(body.rows);
      rows.sort(function (a, b) {
        var x = a.cells[i].textContent.trim(), y = b.cells[i].textContent.trim();
        var nx = parseFloat(x.replace(/[^0-9.-]/g, '')), ny = parseFloat(y.replace(/[^0-9.-]/g, ''));
        var num = x && y && !isNaN(nx) && !isNaN(ny);
        return (num ? nx - ny : x.localeCompare(y)) * (up ? 1 : -1);
      });
      rows.forEach(function (r) { body.appendChild(r); });
    };
  });
</script>
Sortable headers in a few lines. Numbers sort as numbers, so 100 does not land before 9.
Sortable headers in a few lines. Numbers sort as numbers, so 100 does not land before 9.

The numeric check is the part worth copying exactly. Stripping non-digits before parsing is what makes $1,284 and 41 min sort as numbers rather than as text, and localeCompare puts accented names in the right place. And aria-sort on the header is one attribute that makes the sorted state announced rather than merely visible.

Use real table markup

<table>
  <thead><tr><th scope="col">Item</th><th scope="col">Owner</th></tr></thead>
  <tbody><tr><td>Pricing page</td><td>Jae</td></tr></tbody>
</table>

thead, tbody and scope="col" are what let a screen reader announce "Owner, Jae" instead of reading a stream of unlabelled words. A table built from styled divs looks identical and is unusable without sight. See semantic HTML.

Editable cells, and their limit

One attribute makes a cell editable:

<td contenteditable="true">In review</td>

That works immediately. The limit is where the edits go: in a standalone file, nowhere, or at best into the local storage of whichever browser opened it — so four recipients produce four divergent tables that cannot see each other. See the table builder for a generated file that does this, and its honest constraints.

A copy per person ✗ Each edit lives on one machine ✗ No way to merge the changes ✗ Nobody can say which is current ✗ The oldest copy keeps circulating One address ✓ Everyone opens the same page ✓ A correction is seen by all ✓ There is only one current version ✓ Forwarding shares the page, not a copy
A copy per reader versus one address.

When more than one person needs the same rows

Then the table has to live at one address rather than in everyone's Downloads folder. Publish the page and send the link: same rows for everybody, one place to correct a figure.

At one address, everyone looks at the same rows and a correction is typing over the cell.
At one address, everyone looks at the same rows and a correction is typing over the cell.

In NOS the pasted table HTML renders exactly as written and the cells hold clickable text, so a correction is typing over it — and the link you sent keeps pointing at the current table.

Long tables

Past about fifty rows, a page scrolls a long way and the header disappears off the top. Two lines fix that: position: sticky; top: 0 on the header cells keeps the column names in view while the body scrolls under them.

Share the table at an address. Long tables scroll; nobody downloads anything.
Share the table at an address. Long tables scroll; nobody downloads anything.

Past a few hundred rows, add a filter box that hides rows not matching what is typed; it is a dozen lines of script and it turns a wall of data into something a reader can use on a phone.

Copying the table out

A reader will sometimes want the rows in a spreadsheet. A table built from real markup can be selected and pasted straight into one, and the columns survive. A Download CSV button that walks the rows and joins the cells with commas is a few lines more and saves the reader from selecting carefully.

Column order and what to leave out

Put the column people sort by first, the one they read across second, and the identifier last. Drop any column that is the same in every row; it is a heading, not a column.

A table shared as a page is read on a phone more often than on a wide screen, and every column you keep is a column the reader has to scroll to.

  1. Use real table markup and right-align the numbers. thead, tbody, scope="col", and font-variant-numeric: tabular-nums so the digits form a straight edge.
  2. Wrap the table in a scroller for phones. The two-line wrapper above. The table scrolls sideways; the rest of the page stays put.
  3. Paste the table into a NOS document and copy the share link. Share, then Share link, then Create link. Same rows for everybody, one place to correct a figure. Turning HTML into a link is this step.
  4. Correct a figure on the page. Click the cell, type over it. The link everyone has already shows the corrected row, and there is no spreadsheet v3 in anyone's inbox.

Questions people ask

Why not just send a spreadsheet?

Because a spreadsheet has to be downloaded and opened in an application, which on a phone means it usually is not. A page opens in a tap and reads at the screen width.

How do I make a table readable on a phone?

Let it scroll sideways inside its own container rather than squashing the columns. A wrapper with overflow-x: auto does it in one line, and keeps the rest of the page still.

Can readers sort the table?

Yes, with a short script on the column headers. The part to get right is numeric comparison — plain text sorting puts 100 before 9.

Can readers edit it?

Cells can be made editable with one attribute, but in a standalone file each reader's edits stay on their own machine. For shared editing the table needs to live at one address.

Keep reading