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
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.
<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
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:
<table> <colgroup> <col class="col-200" /> <col /> <col class="col-input" /> <col class="col-date" /> <col class="col-edit" /> </colgroup> <thead> <tr> <th colspan="2">Project name</th> <th>Amount</th> <th>Date</th> <th>Edit</th> </tr> </thead> <!-- etc. --> </table>
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
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.
colfeels like it breaks the semantics of the table a bit.
Another possible solution would be to use
vwunits to determine the size of the first column – something like:
if the table has a max-width, you would have to use
clampto set the width of the column once the table reaches its max width
This solution admittedly could become much more complicated depending on the layout of the page surrounding the table.
Interesting. How did you decide on 680px?
I do this all the time with Angular and PrimeNG tables, except I find it safer to use %s instead of pxs. I usually leave one column at “auto”.
Why not just use display grid with table and set trs, thead, tfoot, tbody to display contents. Then you can use power of grid to format the table to your liking.
That is also a way og doing it. I know that there have been issues with css display content messing up the semantics. The issue is still open here: https://github.com/w3c/csswg-drafts/issues/3040
Don’t know if it is still an issue – have not tested it lately