Chris Chris - 5 months ago 12
CSS Question

Strange behavior on table and table-cell with percentage width

I recently stumbled upon a question here where someone was trying to get a proper line-behind behavior on their headers. Something like this:



Their implementation yielded different results across different headers, and hence the question. The implementation was quite odd by using

table
for the element and
table-cell
for the
:before
and
:after
. The resulting behavior was for me quite surprising. I made a test on jsfiddle, have a look.

You'll see that I have two classes;
a
and
b
, where the former has a width of 25% and the latter 40%. Despite this, the
b
is nearly 5 times wider than
a
. Setting them to 40% and 45% respectively makes
b
twice as large as
a
. This is totally unexpected behavior for just a 5% difference. The length of the text seems to affect this too.

I get that this implementation is bad and that it would "break". However, I'd like to know why it breaks the way it does. This makes no sense to me.



div {
display: table;
background: grey;
margin: 10px 0;
}
div:before,
div:after {
width: 25%;
position: relative;
content: '';
border-top: 2px solid black;
display: table-cell;
}
div.b:before,
div.b:after {
width: 45%;
}

<div class="a">text</div>
<div class="a">some more text</div>

<div class="b">text</div>
<div class="b">some more text</div>




Answer

The auto tabular layout is not specified by the standard, so I recommend against using this kind of percentage tricks, which will probably break on some browsers.

However, it seems most browsers behave like this:

  • The ::before and ::after pseudo-elements will take the specified percentage of the table.
  • The text is wrapped inside an anonymous cell which has the width of the text. That will represent the remaining percentage left by the pseudo-elements.
  • The width of the table will be determined by the constraints above.

So if you have width: 25%, the text will take the remaining 50%.

tablewidth = textwidth / 50% = 2 * textwidth
pseudowidth = 25% * tablewidth = 0.5 * textwidth

If you have width: 40%, the text will take the remaining 20%.

tablewidth = textwidth / 20% = 5 * textwidth
pseudowidth = 40% * tablewidth = 2 * textwidth

If you have width: 45%, the text will take the remaining 10%. That is, the table will double its width with respect to the previous case, if the text is the same.

tablewidth = textwidth / 10% = 10 * textwidth
pseudowidth = 45% * tablewidth = 4.5 * textwidth

Of course, things start to get tricky if the desired table width calculated as described above exceeds the containing block. Then

  • The table will be as wide as the containing block
  • The text cell will have the width resulting of resolving the remaining percentage left by the pseudo-elements. That will be less than the preferred width of the text, so the text will wrap into multiple lines.

    However, the text cell won't be narrower than the minimum required width of the text. That will usually be the width of the longest word, but may change due to e.g. word-break: break-all or white-space: nowrap.

  • The remaining space left by the text will be equally distributed among the pseudo-elements.

    If the remaining space is negative, the pseudo-elements will have 0 width, and the table will grow (overflowing the containing block) in order to compensate that.