Content Width Determination with the Box Model
Tags: css, learning, specsreading
Published: 2019-03-10 17:21:12


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.

![](https://d2mxuefqeaa7sj.cloudfront.net/s_46691719D9A10FD85475E1AE5C9BBC1C5717457E8F6BBDBAEABA998CE95A0357_1551847907593_image.png)

I did my basic CSS and they turned out to look like this:

![](https://d2mxuefqeaa7sj.cloudfront.net/s_46691719D9A10FD85475E1AE5C9BBC1C5717457E8F6BBDBAEABA998CE95A0357_1551847870786_image.png)

They actually look pretty neat to me, but the designer insists that they each set their own widths 😝

{% codepen https://codepen.io/wgao19/pen/VRzLvm %}


## 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](https://www.w3.org/TR/CSS2/visudet.html#Computing_widths_and_margins).

There are ten cases here in non-human language 🙈 kill me please.

1. inline, non-replaced elements
2. inline, replaced elements
3. block-level, non-replaced elements in normal flow
4. block-level, replaced elements in normal flow
5. floating, non-replaced elements
6. floating, replaced elements
7. absolutely positioned, non-replaced elements
8. absolutely positioned, replaced elements
9. 'inline-block', non-replaced elements in normal flow
10. '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'](https://www.w3.org/TR/CSS2/box.html#propdef-margin-left) + ['border-left-width'](https://www.w3.org/TR/CSS2/box.html#propdef-border-left-width) + ['padding-left'](https://www.w3.org/TR/CSS2/box.html#propdef-padding-left) + ['width'](https://www.w3.org/TR/CSS2/visudet.html#propdef-width) + ['padding-right'](https://www.w3.org/TR/CSS2/box.html#propdef-padding-right) + ['border-right-width'](https://www.w3.org/TR/CSS2/box.html#propdef-border-right-width) + ['margin-right'](https://www.w3.org/TR/CSS2/box.html#propdef-margin-right) = width of [containing block](https://www.w3.org/TR/CSS2/visudet.html#containing-block-details)

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'](https://www.w3.org/TR/CSS2/visuren.html#propdef-direction) property of the containing block has the value 'ltr', the specified value of ['margin-right'](https://www.w3.org/TR/CSS2/box.html#propdef-margin-right) is ignored and the value is calculated so as to make the equality true. If the value of ['direction'](https://www.w3.org/TR/CSS2/visuren.html#propdef-direction) is 'rtl', this happens to ['margin-left'](https://www.w3.org/TR/CSS2/box.html#propdef-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'](https://www.w3.org/TR/CSS2/visudet.html#propdef-width) is set to 'auto', any other 'auto' values become '0' and ['width'](https://www.w3.org/TR/CSS2/visudet.html#propdef-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 `auto`, then `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.

![](https://d2mxuefqeaa7sj.cloudfront.net/s_46691719D9A10FD85475E1AE5C9BBC1C5717457E8F6BBDBAEABA998CE95A0357_1552186422509_image.png)

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.

![](https://d2mxuefqeaa7sj.cloudfront.net/s_46691719D9A10FD85475E1AE5C9BBC1C5717457E8F6BBDBAEABA998CE95A0357_1552186896507_image.png)

[Here's the CodePen link](https://codepen.io/wgao19/pen/WmOYve) to those two illustrations.

{% codepen https://codepen.io/wgao19/pen/WmOYve %}

And finally,

> If both ['margin-left'](https://www.w3.org/TR/CSS2/box.html#propdef-margin-left) and ['margin-right'](https://www.w3.org/TR/CSS2/box.html#propdef-margin-right) are 'auto', their used values are equal. This horizontally centers the element with respect to the edges of the containing block.

Here’s the `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.

{% codepen https://codepen.io/wgao19/pen/pYwQVR %}

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 `flex-box`, [CodePen](https://codepen.io/wgao19/pen/XGNOXe).
Reading the flexbox specs had been more freshening than I thought.
So I'll follow up a post dedicated for it :]

{% codepen https://codepen.io/wgao19/pen/XGNOXe %}

じゃあ、またね 🤞

## Links

- [CSS 2 - 10 Visual Formatting Model Details](https://www.w3.org/TR/CSS2/visudet.html)