Turning Lists into Tables for Markdown

I think that ta­bles are the best way to pre­sent a lot of struc­tured in­for­ma­tion. Un­for­tu­nately, they’re so damn hard to do in Markdown, that I don’t use them as much as I’d like.1 There re­ally has to be an eas­ier way to do ta­bles.

tl;dr

How Markdown Does Tables

Original Markdown did­n’t have ta­bles. If you wanted ta­bles, you just used HTML. That’s fine for ocas­sional ta­bles but not when you’re do­ing a lot of them.2

Most mod­ern Markdown proces­sors fol­low GitHub Flavored Markdown’s (GFM) implementation of ta­bles. So this:

| State         | Capital |
| :------------ | :------ |
| New York      | Albany  |
| Nebraska      | Lincoln |
| New Hampshire | Concord |

…is ren­dered like this:

State Capital
New York Albany
Nebraska Lincoln
New Hampshire Concord

Not too bad. And there are some nice tools, like Brett Terpstra’s Markdown Service Tools, that help you for­mat them.

But what if you want a table with com­pli­cated cells:

State Capital & Top Cities
New York Albany
  • New York City
    A lot of people think that this is the capital
  • Buffalo
  • Rochester
Nebraska Lincoln
  • Omaha
  • Lincoln
  • Bellevue
New Hampshire Concord
  • Manchester
  • Nashua
  • Concord


In GFM, table cells can be only one line, so you can’t use mul­ti­line Markdown in­side them. You can use raw HTML writ­ten all on one line, like this, but that’s te­dious to write and im­pos­si­ble to main­tain.

| State         | Capital & Top Cities                                                                                                      |
| :------------ | :------------------------------------------------------------------------------------------------------------------------ |
| New York      | Albany<ul><li>New York City<br>A lot of people think that this is the capital</li><li>Buffalo</li><li>Rochester</li></ul> |
| Nebraska      | Lincoln<ul><li>Omaha</li><li>Lincoln</li><li>Bellevue</li></ul>                                                           |
| New Hampshire | Concord<ul><li>Manchester</li><li>Nashua</li><li>Concord</li></ul>                                                        |

GFM ta­bles are great, as long as you can fit every­thing on one line. Is there a way around this that does­n’t in­volve in­tro­duc­ing new Markdown el­e­ments or us­ing a pre­proces­sor?

How reStructuredText Does It

In re­Struc­tured­Text (RST) you can use the list-table directive to ex­press a table as a list:

.. list-table:: Title
   :widths: 25 25 50
   :header-rows: 1

   - - Heading row 1, column 1
     - Heading row 1, column 2
     - Heading row 1, column 3
   - - Row 1, column 1
     -
     - Row 1, column 3
   - - Row 2, column 1
     - Row 2, column 2
     - Row 2, column 3

This gets ren­dered as a table:

Title
Heading row 1, column 1 Heading row 1, column 2 Heading row 1, column 3
Row 1, column 1   Row 1, column 3
Row 2, column 1 Row 2, column 2 Row 2, column 3

Here’s what the HTML rendered by reStructuredText looks like.

Can We Do That in Markdown?

Can we bor­row this list-to-table tech­nique from re­Struc­tured­Text and use it in Markdown? I think so.

We want to start with a list like this:

<div class="t" markdown="1">

- - **Property**
  - **Description**
- - `inputPath`
  - Path to this file including the `input` directory.
- - `outputPath`
  - Path to the rendered file.
    `articles/finding-oz/index.html`
- - `fileSlug`
  - Short name from the file name.
    [There are rules](https://www.11ty.io/docs/data/#fileslug).

</div>

That div class="t" is how we let Markdown know which lists get ren­dered as lists and which ones get ren­dered as ta­bles. 3

Using our reg­u­lar CSS stylesheet, the list above ren­ders like a nor­mal list.

    • Property
    • Description
    • inputPath
    • Path to this file including the input directory.
    • outputPath
    • Path to the rendered file. articles/finding-oz/index.html

Let’s look at the HTML of that so we can see what CSS we need to write. I for­mat­ted the li and ul elements to show how they can be arranged and a table-like way.

<div class="t" markdown="1"><ul>
 <li><ul>
  <li><strong>Property</strong></li>
  <li><strong>Description</strong></li>
 </ul></li>
 <li><ul>
  <li><code>inputPath</code></li>
  <li>Path to this file including the <code>input</code> directory.</li>
 </ul></li>
 <li><ul>
  <li><code>outputPath</code></li>
  <li>Path to the rendered file. <code>articles/finding-oz/index.html</code></li>
 </ul></li>
 <li><ul>
  <li><code>fileSlug</code></li>
  <li>Short name from the file name. <a href="https://www.11ty.io/docs/data/#fileslug">There are rules</a>.</li>
 </ul></li>
</ul></div>

Mapping Table Elements to List Elements

We can now map the table el­e­ments to list el­e­ments like this:4

    • Table Elements
    • List Elements
    • table
    • <div><ul>
    • tr
    • <div><ul><li><ul>
    • td
    • <div><ul><li><ul><li>

At some point in get­ting this to work, I came across this piece of CSS that gives the display prop­erty of each of the table-re­lated el­e­ments.

table    { display: table }
tr       { display: table-row }
thead    { display: table-header-group }
tbody    { display: table-row-group }
tfoot    { display: table-footer-group }
col      { display: table-column }
colgroup { display: table-column-group }
td, th   { display: table-cell }
caption  { display: table-caption }

Given our table/​list map­ping and armed with the knowl­edge of how our HTML is ren­dered, we can use this CSS to make the list el­e­ments be­have like ta­bles. Probably.

div[class~="t"] > ul {
  display: table
  list-style: none;
}

div[class~="t"] > ul > li > ul {
  display: table-row
}

div[class~="t"] > ul > li > ul > li {
  display: table-cell
}

Here’s what that looks like. It kind of works, but the spac­ing is way off, to say noth­ing of the bul­let points.

    • Property
    • Description
    • inputPath
    • Path to this file including the input directory.
    • outputPath
    • Path to the rendered file. articles/finding-oz/index.html

Styling a List as a Table

When I hit the wall in CSS, I just start pulling levers and push­ing but­tons. I played with this code­pen, and even­tu­ally came to this CSS. I don’t know why it works. If you do know, please send me a note.

div[class~="t"] > ul {
  display: table;
  list-style: none;
  width: 100%;
  margin: 0;
  padding: 0;
}

div[class~="t"] > ul > li {
    display: table-row-group;
}

div[class~="t"]  > ul > li:nth-child(even) {
    background-color: #eee;
}

div[class~="t"]  > ul > li:nth-child(odd) {
    background-color: #fff;
}

div[class~="t"] > ul > li > ul {
    display: table-row;
}

div[class~="t"] > ul > li > ul > li {
  display: table-cell;
  padding: 0 .25em;
}

This is what the re­sult looks like. Not too bad.

    • Property
    • Description
    • inputPath
    • Path to this file including the input directory.
    • outputPath
    • Path to the rendered file. articles/finding-oz/index.html

OMG It Works

Remember our com­plex table? We should be able to ex­press the table as a list, and get a table. This is the Markdown:

<div class="t" markdown="1">

- - **State**
  - **Capital & Top Cities**
- - New York
  - Albany
    - New York City<br>
      A lot of people think that this is the capital
    - Buffalo
    - Rochester
- - Nebraska
  - Lincoln
    - Omaha
    - Lincoln
    - Bellevue
- - New Hampshire
  - Concord
    - Manchester
    - Nashua
    - Concord

</div>

And this is the re­sult. Pretty good.

    • State
    • Capital & Top Cities
    • New York
    • Albany
      • New York City
        A lot of people think that this is the capital
      • Buffalo
      • Rochester
    • Nebraska
    • Lincoln
      • Omaha
      • Lincoln
      • Bellevue
    • New Hampshire
    • Concord
      • Manchester
      • Nashua
      • Concord

Nested Tables?

Although we did­n’t de­sign the ta­bles to be nested, it’s to­tally pos­si­ble to do it:

    • State
    • Capital & Top Cities
    • New York

    • Albany

        • City
        • Pop*
        • New York City
          A lot of people think that this is the capital
        • 8.6 million
        • Buffalo
        • 250,000
        • Rochester
        • 210,000
    • Nebraska

    • Lincoln

        • City
        • Pop
        • Omaha
        • 408,958
        • Lincoln
        • 258,370
        • Bellevue
        • 50,137
    • New Hampshire

    • Concord

        • City
        • Pop
        • Manchester
        • 110,000
        • Nashua
        • 88,000
        • Concord
        • 43,000
The Markdown for that is a little messy, but still editable and legible.

What Now?

It works5 well enough for my pur­poses, but there’s still more to do:

Most Markdown proces­sors can han­dle lists that don’t re­ally have a first el­e­ment:

- - `inputPath`
  - Path to this file including the `input` directory.
- - `outputPath`
  - Path to the rendered file.
    `articles/finding-oz/index.html`

Some have dif­fer­ent ways of al­low­ing Mark­down within HTML, and some proces­sors don’t al­low it at all.

Most proces­sors ren­der the HTML for lists in sim­i­lar ways. There may be vari­a­tions for for­mat­ting and white­space, but as long as the HTML is struc­turally the same, this tech­nique should work.

If you want to take on com­pat­i­bil­ity, Ba­bel­mark 3 is a good place to start. This clip will show you which proces­sors don’t like Markdown in their HTML and which don’t like the way I make the lists.

  1. Not be­cause they’re te­dious to get right, but be­cause main­tain­ing them is er­ror prone. ↩︎

  2. There are ex­ter­nal tools that help, but I want to deal with straight Markdown as much as pos­si­ble. ↩︎

  3. Different Markdown processors have dif­fer­ent rules about em­bed­ding Mark­down in­side HTML. Many use the prop­erty markdown="1" to al­low it. The one I’m us­ing, markdown-it, fol­lows the CommonMark spec which re­quires that the <div> and </div> be fol­lowed by a blank line. ↩︎

  4. Dogfooding! ↩︎

  5. on my ma­chine ↩︎