Render tables using lists in markdown
Sat Feb 16 2019I think that tables are the best way to present a lot of structured information. Unfortunately, they’re so damn hard to do in Markdown, that I don’t use them as much as I’d like.1 There really has to be an easier way to do tables.
tl;dr
- use the
display
property to make list elements behave like table elements - the CSS you’re looking for
- a codepen if you just want to see the code
How Markdown does tables
Original Markdown didn’t have tables. If you wanted tables, you just used HTML. That’s fine for ocassional tables but not when you’re doing a lot of them.2
Most modern Markdown processors follow GitHub Flavored Markdown’s (GFM) implementation of tables. So this:
| State | Capital |
| :------------ | :------ |
| New York | Albany |
| Nebraska | Lincoln |
| New Hampshire | Concord |
…is rendered 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 format them.
But what if you want a table with complicated cells:
State | Capital & Top Cities |
---|---|
New York | Albany
|
Nebraska | Lincoln
|
New Hampshire | Concord
|
In GFM, table cells can be only one line, so you can’t use multiline Markdown inside them. You can use raw HTML written all on one line, like this, but that’s tedious to write and impossible to maintain.
| 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 tables are great, as long as you can fit everything on one line. Is there a way around this that doesn’t involve introducing new Markdown elements or using a preprocessor?
How reStructuredText does it
In reStructuredText (RST)
you can use the list-table
directive to express 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 rendered as a table:
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 borrow this list-to-table technique from reStructuredText 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 rendered as lists
and which ones get rendered as tables.
3
Using our regular CSS stylesheet,
the list above renders like
a normal list.
-
- 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.
Let’s look at the HTML of that
so we can
see what CSS we need to write.
I formatted 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 elements
to list elements 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 getting this to work,
I came across
this piece of CSS
that gives the
display
property
of each of the table-related elements.
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 mapping and armed with the knowledge of how our HTML is rendered, we can use this CSS to make the list elements behave like tables. 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 spacing is way off,
to say nothing of the bullet points.
-
- 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.
Styling a list as a table
When I hit the wall in CSS, I just start pulling levers and pushing buttons. I played with this codepen, and eventually 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 result 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
-
fileSlug
- Short name from the file name. There are rules.
OMG it works
Remember our complex table? We should be able to express 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 result. Pretty good.
-
- State
- Capital & Top Cities
-
- New York
- Albany
- New York City
A lot of people think that this is the capital - Buffalo
- Rochester
- New York City
-
- Nebraska
- Lincoln
- Omaha
- Lincoln
- Bellevue
-
- New Hampshire
- Concord
- Manchester
- Nashua
- Concord
Nested Tables?
Although we didn’t design the tables to be nested, it’s totally possible 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
- New York City
-
- 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 purposes, but there’s still more to do:
- Captions
- More/better CSS
- Compatibility
Most Markdown processors can handle lists that don’t really have a first element:
- - `inputPath`
- Path to this file including the `input` directory.
- - `outputPath`
- Path to the rendered file.
`articles/finding-oz/index.html`
Some have different ways of allowing Markdown within HTML, and some processors don’t allow it at all.
Most processors render the HTML for lists in similar ways. There may be variations for formatting and whitespace, but as long as the HTML is structurally the same, this technique should work.
If you want to take on compatibility, Babelmark 3 is a good place to start. This clip will show you which processors don’t like Markdown in their HTML and which don’t like the way I make the lists.
Not because they’re tedious to get right, but because maintaining them is error prone. ↩︎
There are external tools that help, but I want to deal with straight Markdown as much as possible. ↩︎
Different Markdown processors have different rules about embedding Markdown inside HTML. Many use the property
markdown="1"
to allow it. The one I’m using,markdown-it
, follows the CommonMark spec which requires that the<div>
and</div>
be followed by a blank line. ↩︎Dogfooding! ↩︎
on my machine ↩︎