Content Width Determination with the Box Model
I was working on a this design that contains a few badges aligned like this on a corner. The content of those badges is not fixed, so I do not know the exact width of each of them. However, the design requires that they have flexible widths.
I did my basic CSS and they turned out to look like this:
They actually look pretty neat to me, but the designer insists that they each set their own widths 😝
Intuition from the specs
Once again I decided to look for answers in the specs. Luckily since I’ve already read many sections of the specs, I roughly knew where to look — check how CSS determines content width.
There are ten cases here in non-human language 🙈 kill me please.
- inline, non-replaced elements
- inline, replaced elements
- block-level, non-replaced elements in normal flow
- block-level, replaced elements in normal flow
- floating, non-replaced elements
- floating, replaced elements
- absolutely positioned, non-replaced elements
- absolutely positioned, replaced elements
- 'inline-block', non-replaced elements in normal flow
- 'inline-block', replaced elements in normal flow
If you’re like me, registering “thesaurus” while reading CSS specs, let’s exchange dictionary: I think of “browsers” whenever I see “user agents”. Your turn now..
Anyway, it seems to me that those 10 cases are alternating between “non-replaced” and “replaced” elements. My thesaurus for “replaced” elements is “img”. And I’m not concerned about images now, this cuts my target in half. My elements are not “inline”, nor “floating”, not “absolutely-positioned”, and not “inline-block”. Then I found my case!
10.3.3 Block-level, non-replaced elements in normal flow
The following constraints must hold among the used values of the other properties: 'margin-left' + 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' + 'margin-right' = width of containing block
Now, this equation seems like a tautology at first sight. But what’s really holding the checks here is this: sometimes there’d be conflicting (CSS’s term: over-constrained) values, some other times there are not enough values. This section describes how those cases should be handled such that this equation is satisfied:
If 'width' is not 'auto' and 'border-left-width' + 'padding-left' + 'width' + 'padding-right' + 'border-right-width' (plus any of 'margin-left' or 'margin-right' that are not 'auto') is larger than the width of the containing block, then any 'auto' values for 'margin-left' or 'margin-right' are, for the following rules, treated as zero.
This is saying that if the
width is not
auto and the total space needed is larger than the space there is, then any
auto goes to 0.
If all of the above have a computed value other than 'auto', the values are said to be "over-constrained" and one of the used values will have to be different from its computed value. If the 'direction' property of the containing block has the value 'ltr', the specified value of 'margin-right' is ignored and the value is calculated so as to make the equality true. If the value of 'direction' is 'rtl', this happens to 'margin-left' instead.
This is saying that if all of those values are specified, then one of those specified value will give up its constraint and just take up the remaining space. Depending whether you are reading left-to-right or right-to-left, whichever side that comes later has the
margin on that side become this candidate.
If there is exactly one value specified as 'auto', its used value follows from the equality.
Makes total sense, right?
If 'width' is set to 'auto', any other 'auto' values become '0' and 'width' follows from the resulting equality.
I understand this line as follows:
width has an
auto of higher precedence over other
auto values, i.e., if everyone is
width takes the entire remaining space and everyone else becomes 0. Again, it makes sense because I actually don’t want those paddings and margins to appear unless I specify them.
It follows that
width will adjust itself if there are specified values around it, while still taking higher priority over other
auto values, taking up the entire remaining space.
Here's the CodePen link to those two illustrations.
If both 'margin-left' and 'margin-right' are 'auto', their used values are equal. This horizontally centers the element with respect to the edges of the containing block.
margin: 0 auto trick everyone’s using 🙂
Many of the remaining 9 sections either share the similar spirit, or follows a logic that serves its context quite sensibly.
A few solutions to the problem
display: inline on children sets the widths free, but it also makes them appear on the same line.
Floating the elements
float: right also sets the widths free for those children nodes, but I need to set a specific width for the parent.
The parent width needs to be such that it forces each element to wrap.
Otherwise the children nodes will still appear on the same line, which does not yield the desired design.
Nevertheless, this does not feel like a neat implementation and so I'm not happy with it.
One another solution is to wrap each of them in an extra
block div, then put the clip inside that has
float: right or
display: inline that gives them free widths. But I’m not a fan of that, neither.
Flexbox to create rows of flexible widths
I eventually went with
Reading the flexbox specs had been more freshening than I thought.
So I'll follow up a post dedicated for it :]