Tables are great for enhancing scannability: readers can easily browse rows in the first column and access the information they’re looking for in relevant cells of the corresponding row.
However, tables display every column in front of every reader, whatever their needs, at once. This contradicts a major principle of information architecture and design: layering, or progressive disclosure, which information-design researcher David Farkas dubbed a “safety net” for minimalist documentation. (An earlier article, Less is more, comes at the same idea from the psychology side.)
With modern web frameworks, tech writers can now allow users to display or hide table columns or sort rows based on different criteria. On my documentation site, for example, you can sort the rows of the API endpoints, target formats, and manual layout options tables alphabetically, then restore their original order.
This article illustrates how to sort rows and hide columns in a live 1978 NBS table.
Why tables overwhelm
Everything at once
A table has no reading order of its own. Every cell has the same weight: the column that answers the reader’s question looks exactly like the three that qualify it. So the reader does the sorting, every time, for every table. Which column matters? Which ones can wait? A writer who knows the answer and doesn’t act on it leaves that work to each reader separately.
The usual minimalist fix is to cut the columns that most readers don’t need. That makes the table easier to read and useless to the reader who needed exactly the column that got cut.
The NBS table
For example, here is a table showing how 50 studies compared software processing times.
| Reported? | Central tendency (mean or median) | Standard deviation | Individual problems discussed | Worst case analysis |
|---|---|---|---|---|
| No | 36 | 47 | 26 | 48 |
| Yes | 14 | 3 | 24 | 2 |
The table requires users to:
- Find the row that matches their question, Yes or No.
- Pick the one column that answers it.
- Ignore the three that don’t.
- Keep enough context in their short-term memory to read the number correctly.
But columns aren’t equally important to every reader.
Someone who wants to know whether these studies reported an average at all needs one column: 14 did, 36 didn’t.
Someone checking how rigorous the reporting was needs the other three.
Progressive disclosure
Layering
- Essential comparison: the Central tendency view
- Supporting measures: one selection away
- Interpretation: what the numbers mean
- Source: the full paper
The first two layers live in the table. The view options run in the same order:
-
Central tendency
-
Supporting measures
-
All columns
Disclosure vs. hiding
Can a reader who has never seen the page find everything? The control shows which view is on, and users can always select to view All columns.
Hiding
Removes information from view
- Default: 2 of 8 values.
- The rest: gone, or behind an unlabeled control.
- The reader: sees a clean, short table and has no reason to think there’s more.
Disclosure
Keeps access, reduces what shows first
- Default: 2 of 8 values, and a control that says so.
- The rest: the other 6, a single selection away.
- The reader: can see which view is on and get everything back.
Choosing a mechanism
Three mechanisms cover most layered documentation. They aren’t interchangeable:
The details element
Native HTML
- Best for: optional material most readers skip, such as interpretation.
- Advantage: collapsed by default, opens on request, no JavaScript.
- Limitation: one block at a time; it can’t switch which columns of a table show.
Tabs
Scripted component
- Best for: alternatives the reader picks one of, such as one procedure per platform.
- Advantage: one panel at a time, always in the same place.
- Limitation: the reader can’t see two panels at once to compare them, and for a table, each tab holds its own copy of the data.
View options
Custom, on one table
- Best for: one table whose columns serve different readers.
- Advantage: one copy of the content, and All columns is always offered.
- Limitation: needs JavaScript, with the full table as the fallback, and it’s custom code to maintain.
Use <details> when the extra material is optional and makes sense on its own.
Use tabs when the alternatives exclude each other and nobody needs to compare them.
Use view options when one table serves several readers who may still want to compare across views, as with the NBS table, or with A practical comparison of information types, which opens a comparison of Markdown, DITA, and OpenAPI on an overview and keeps typing and validation one selection away.
Implementation
Markup for layers
The table is a single <table>, wrapped in the TableViews component, and each view is only a list of column IDs.
MDX: the three views of the NBS table
views={[
{ id: "central-tendency", label: "Central tendency", columns: ["central-tendency"] },
{ id: "supporting", label: "Supporting measures", columns: ["standard-deviation", "individual-problems", "worst-case"] },
{ id: "all", label: "All columns", columns: ["central-tendency", "standard-deviation", "individual-problems", "worst-case"] },
]}
The order of the list is the order of the layers. Choosing a view sets the native hidden attribute on the cells of the columns it doesn’t show. The first column always stays, since it names the rows. Nothing is copied, so no view can drift out of sync with the others.
Most readers can skip a <details> element, which the browser renders collapsed under its <summary>.
HTML: collapsed state
<details>
<summary>What the counts mean</summary>
<p>Each cell counts papers out of all 50…</p>
</details>
HTML: expanded state
<details open>
<summary>What the counts mean</summary>
<p>Each cell counts papers out of all 50…</p>
</details>
Example: an API comparison
Let’s take another example: a table comparing REST, GraphQL, and gRPC across six criteria. Options are columns. Criteria are rows, so the layers are rows. What goes first depends on the question the reader brings.
Layered for someone choosing an approach, the typical use row comes first:
| REST API | GraphQL | gRPC | |
|---|---|---|---|
| Typical use | Web APIs | Flexible data queries | Service-to-service communication |
Client control and streaming
| REST API | GraphQL | gRPC | |
|---|---|---|---|
| Client controls fields | Usually no | Yes | Defined by service |
| Streaming | Limited | Supported | Strong support |
Representation, transport, and data format
| REST API | GraphQL | gRPC | |
|---|---|---|---|
| Primary representation | Resources | Graph | Services |
| Transport | HTTP | HTTP | HTTP/2 |
| Data format | JSON | JSON | Protocol Buffers |
What changes is which rows the reader meets first.
Before you layer a table
Run through these questions before you publish:
- Does the first view answer the question most readers bring?
- Is everything outside the first view genuinely secondary, and does the text say why the first layer is first?
- Do the views run from the first layer to everything, with everything always offered?
- Does every view keep the row labels, so no value appears without its context?
- Does the control show which view is on?
- Without JavaScript, does the reader get the full table?
- Can the reader switch views with the keyboard?
- Would a
<details>block or tabs be simpler than views on one table?