# From tables to layers: progressive disclosure in a documentation table

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"](https://direct.mit.edu/books/edited-volume/2438/chapter-abstract/64685/Layering-as-a-Safety-Net-for-Minimalist?redirectedFrom=fulltext) for minimalist documentation. (An earlier article, [Less is more](https://redaction-technique.org/less-is-more-layering), 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](https://docs.redaction-technique.org/en/about-the-api/#endpoints), [target formats](https://docs.redaction-technique.org/en/about-this-blog/#target-formats), and [manual layout options](https://docs.redaction-technique.org/en/tech-writing-process/source-format/#key-principles) tables alphabetically, then restore their original order.

This article illustrates how to sort rows and hide columns in a live 1978 NBS table.

<Callout type="info" title="In this article">
<ul class="list-disc pl-5 m-0 space-y-1">
  <li>Why a full table makes every reader do the same sorting work</li>
  <li>What progressive disclosure changes, and how it differs from hiding</li>
  <li>Three ways to build it: <code>&lt;details&gt;</code>, tabs, and view options on one table</li>
  <li>A checklist for deciding which column comes first, and how the reader gets to the rest</li>
</ul>
</Callout>

## Why tables overwhelm

<Callout type="important" title="The problem">
One table is carrying too many jobs. Every reader gets every column, whatever question they came with.
</Callout>

### 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.

<TableViews
  columns={{
    "central-tendency": "Central tendency (mean or median)",
    "standard-deviation": "Standard deviation",
    "individual-problems": "Individual problems discussed",
    "worst-case": "Worst case analysis",
  }}
  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"] },
  ]}
>

<div class="table-container my-6 not-prose">
  <table class="responsive-table" aria-label="Table 4.1. How processing times were reported">
    <caption class="[@media(max-width:768px)]:block text-left text-sm font-semibold text-ink mb-2">Table 4.1. How processing times were reported</caption>
    <thead>
      <tr class="border-b border-rule bg-paper">
        <th scope="col" class="text-left">Reported?</th>
        <th scope="col" class="text-right">Central tendency (mean or median)</th>
        <th scope="col" class="text-right">Standard deviation</th>
        <th scope="col" class="text-right">Individual problems discussed</th>
        <th scope="col" class="text-right">Worst case analysis</th>
      </tr>
    </thead>
    <tbody class="divide-y divide-rule text-sm text-ink">
      <tr>
        <th scope="row" class="row-header">No</th>
        <td data-label="Central tendency (mean or median)" class="text-right">36</td>
        <td data-label="Standard deviation" class="text-right">47</td>
        <td data-label="Individual problems discussed" class="text-right">26</td>
        <td data-label="Worst case analysis" class="text-right">48</td>
      </tr>
      <tr>
        <th scope="row" class="row-header">Yes</th>
        <td data-label="Central tendency (mean or median)" class="text-right">14</td>
        <td data-label="Standard deviation" class="text-right">3</td>
        <td data-label="Individual problems discussed" class="text-right">24</td>
        <td data-label="Worst case analysis" class="text-right">2</td>
      </tr>
    </tbody>
  </table>
</div>

</TableViews>

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

<Callout type="important" title="The solution">
Reveal information in the order readers need it. Nothing is removed: the rest stays labeled, visible in the control, and one selection away.
</Callout>

### Layering

The first two layers live in the table. The view options run in the same order:

1. **Central tendency**

2. **Supporting measures**

3. **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**.

<div class="not-prose grid gap-4 sm:grid-cols-2 my-6">
  <ConceptCard title="Hiding" subtitle="Removes information from view">
    <ul>
      <li><strong>Default:</strong> 2 of 8 values.</li>
      <li><strong>The rest:</strong> gone, or behind an unlabeled control.</li>
      <li><strong>The reader:</strong> sees a clean, short table and has no reason to think there's more.</li>
    </ul>
  </ConceptCard>
  <ConceptCard title="Disclosure" subtitle="Keeps access, reduces what shows first">
    <ul>
      <li><strong>Default:</strong> 2 of 8 values, and a control that says so.</li>
      <li><strong>The rest:</strong> the other 6, a single selection away.</li>
      <li><strong>The reader:</strong> can see which view is on and get everything back.</li>
    </ul>
  </ConceptCard>
</div>

### Choosing a mechanism

Three mechanisms cover most layered documentation. They aren't interchangeable:

<div class="not-prose grid gap-4 lg:grid-cols-3 my-6">
  <ConceptCard title="The details element" subtitle="Native HTML">
    <ul>
      <li><strong>Best for:</strong> optional material most readers skip, such as interpretation.</li>
      <li><strong>Advantage:</strong> collapsed by default, opens on request, no JavaScript.</li>
      <li><strong>Limitation:</strong> one block at a time; it can't switch which columns of a table show.</li>
    </ul>
  </ConceptCard>
  <ConceptCard title="Tabs" subtitle="Scripted component">
    <ul>
      <li><strong>Best for:</strong> alternatives the reader picks one of, such as one procedure per platform.</li>
      <li><strong>Advantage:</strong> one panel at a time, always in the same place.</li>
      <li><strong>Limitation:</strong> 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.</li>
    </ul>
  </ConceptCard>
  <ConceptCard title="View options" subtitle="Custom, on one table">
    <ul>
      <li><strong>Best for:</strong> one table whose columns serve different readers.</li>
      <li><strong>Advantage:</strong> one copy of the content, and <strong>All columns</strong> is always offered.</li>
      <li><strong>Limitation:</strong> needs JavaScript, with the full table as the fallback, and it's custom code to maintain.</li>
    </ul>
  </ConceptCard>
</div>

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](https://docs.redaction-technique.org/en/toolkit/information-types/#a-practical-comparison), 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**

```js
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**

```html
<details>
  <summary>What the counts mean</summary>
  <p>Each cell counts papers out of all 50…</p>
</details>
```

**HTML: expanded state**

```html
<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 |

<details>
<summary>Client control and streaming</summary>

|                        | REST API   | GraphQL   | gRPC               |
| ---------------------- | ---------- | --------- | ------------------ |
| Client controls fields | Usually no | Yes       | Defined by service |
| Streaming              | Limited    | Supported | Strong support     |

</details>

<details>
<summary>Representation, transport, and data format</summary>

|                        | REST API  | GraphQL | gRPC             |
| ---------------------- | --------- | ------- | ---------------- |
| Primary representation | Resources | Graph   | Services         |
| Transport              | HTTP      | HTTP    | HTTP/2           |
| Data format            | JSON      | JSON    | Protocol Buffers |

</details>

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?

## Key takeaway

<Callout type="important" title="In one sentence">
Progressive disclosure doesn't remove information. It decides the order readers meet it in: the column most of them came for first, everything else one labeled selection away.
</Callout>

<small>*Hero image: ["Layers"](https://www.flickr.com/photos/snowpeak/12547422744/) by [John Fowler](https://www.flickr.com/photos/snowpeak/), licensed under [CC BY 2.0](https://creativecommons.org/licenses/by/2.0/).*</small>

---

Source: https://redaction-technique.org/from-tables-to-layers-responsive-stacking
