The good ol’ <table> tag is the most semantic HTML for showing tabular data. But I find it very hard to control how the table is presented, particularly column widths in a dynamic environment where you might not know how much content is going into each table cell. In some cases, one column is super wide while others are scrunched up. Other times, we get equal widths, but at the expense of a column that contains more content and needs more space.
But I found a CSS tricks-y workaround that helps make things a little easier. That’s what I want to show you in this post.
First we need to understand how layout is handled by the browser. We have the table-layout property in CSS to define how a table should distribute the width for each table column. It takes one of two values:
Let us start with a table without defining any widths on its columns. In other words, we will let the browser decide how much width to give each column by applying table-layout: auto on it in CSS. As you will notice, the browser does its best with the algorithm it has to divide the full available width between each column.
If we swap out an auto table layout with table-layout: fixed, then the browser will merely divide the full available space by the total number of columns, then apply that value as the width for each column:
But what if we want to control the widths of our columns? We have the <colgroup> element to help! It consists of individual <col> elements we can use to specify the exact width we need for each column. Let’s see how that works in with table-layout: auto:
I have inlined the styles for the sake of illustration.
The browser is not respecting the inline widths since they exceed the amount of available table space when added up. As a result, the table steals space from the columns so that all of the columns are visible. This is perfectly fine default behavior.
How does <colgroup> work with table-layout: fixed. Let’s find out:
This doesn’t look good at all. We need the column with a bunch of content in it to flex a little while maintaining a fixed width for the rest of the columns. A fixed table-layout value respects the width — but so much so that it eats up the space of the column that needs the most space… which is a no-go for us.
This could easily be solved if only we could set a min-width on the column instead of a width. That way, the column would say, “I can give all of you some of my width until we reach this minimum value.“ Then the table would simply overflow its container and give the user a horizontal scroll to display the rest of the table. But unfortunately, min-width on table columns are not respected by the <col> element.
The solution is to fake a min-width and we need to be a bit creative to do it.
We can add an empty <col> as the second column for our <colgroup> in the HTML and apply a colspan attribute on the first column so that the first column takes up the space for both columns:
<col class=”col-200″ />
<col class=”col-input” />
<col class=”col-date” />
<col class=”col-edit” />
<th colspan=”2″>Project name</th>
<!– etc. –>
Note that I have added classes in place of the inline styles from the previous example. The same idea still applies: we’re applying widths to each column.
The trick is that relationship between the first <col> and the empty second <col>. If we apply a width to the first <col> (it’s 200px in the snippet above), then the second column will be eaten up when the fixed table layout divides up the available space to distribute to the columns. But the width of the first column (200px) is respected and remains in place.
Voilà! We have a faux min-width set on a table cell. The first cell flexes as the available space changes and the table overflows for horizontal scrolling just as we hoped it would.
(I added a little sticky positioning to the first column there.)
Let’s not totally forget about accessibility here. I ran the table through NVDA on Windows and VoiceOver on macOS and found that all five columns are announced, even if we’re only using four of them. And when the first column is in focus, it announces, “Column one through two”. Not perfectly elegant but also not going to cause someone to get lost. I imagine we could throw an aria-hidden attribute on the unused column, but also know ARIA isn’t a substitute for poor HTML.
I’ll admit, this feels a little, um, hacky. But it does work! Let me know if you have a different approach in the comments… or know of any confusions this “hack” might bring to our users.