Observation: Forced Wrapping
- Published on:
- Categories:
- CSS Flex, CSS 61, Observation 9
- Current music:
- Ling Tosite Sigure — キミトオク
- Current drink:
- Plain Water
Introduction
Back in August, I saw a Mastodon thread by Anne Sturdivant about how she was styling titles, in which she described a wish for a certain wrapping behavior:
I’m finding myself wanting a variant of
text-wrap: balance
where you can specify a minimum number of lines for the text to wrap so that it would never appear on one line without having to insert a<br>
element.
Curiously enough, I remembered one interesting aspect of flexbox that could help to achieve what she wanted, and I shared it with her.
For quite a while, I wanted to write an “observation” post about it — and, finally, here we are! Maybe you’ll find it useful one day too.
The “Forced Wrapping” Technique
The code behind this technique is very short:
Yep, that’s just four declarations, with one for adding a pseudo-element!
display: flex
— first, we must make our element a flex container.max-inline-size: max-content
— then, we need to tell our element that it should size itself in the inline axis based on the maximum intrinsic size of its content.content: ""
— we need a pseudo-element that will go after our text content. That could be an actual element, the main requirement is for it to go after the text. Otherwise, when the content wraps, it will take up the space at the beginning.flex-basis: 0.1px
— finally, we make this pseudo-element to have some tiny positiveflex-basis
value. I found values less than0.1px
to not work well with certain values of browser zoom, but0.1px
seems to work consistently across all browsers.
Why Does It Work?
So, what happens here? Let’s peek at some definitions. First, for max-inline-size: max-content
:
The box’s “ideal” size in the inline axis. Usually the narrowest inline size it could take while fitting around its contents if none of the soft wrap opportunities within the box were taken.
In other words, if our text doesn’t wrap, and there is nothing else near it, our container will be sized to fit the text inside. Let’s look at two headers: one without the forced-wrapping
class, and another with it:
You might’ve noticed from the initial example that the second title had an unusual width. In this second example, you can see why: that is the inner text’s max-content
value!
If we now look at the “Flex Layout Algorithm”, specifically at the second step, “Line Length Determination”, we will find this:
if that dimension of the flex container is being sized under a min or max-content constraint, the available space in that dimension is that constraint
This means that the space available for all our flex items is calculated based on the max-content
(in our case) before any of the flex properties will be applied. So, when we calculate that max-content
, we only look at the original, non-flex dimensions of our elements, and our ::after
is empty; thus it contributes 0px
to the “max-content constraint”, with the actual text being the only real contributor.
The flex-basis
on the pseudo-element enters the algorithm later. What happens next is that, with a positive value, it is subtracted from the available space from our text’s anonymous box. From the flex-basis
description:
This component sets the flex-basis longhand, which specifies the flex basis: the initial main size of the flex item, before free space is distributed according to the flex factors.
And, because our container was sized with our text’s size, subtracting anything results in the text wrapping! Of course, unless it was wrapping already — in which case the tiny size of the ::after
won’t have any visible impact.
Things to Note
As mentioned in the original thread, this looks the best with text-wrap: balance
. It is not present in the code I showed, as it is not strictly required for the technique to work, but it is applied from my general blog’s CSS.
If some of the text inside the title would need to have an additional element somewhere in the text, we’d need to wrap the whole text into a span, otherwise, due to the flex context, things won’t look as expected:
So if you want to use this with unknown content, it might be better to have this extra wrapper present. But maybe one day we’ll have the :contents
pseudo-element which we could use to add an explicit wrapper around everything.
Sometimes, a title could look better when it stays on one line, in which case a non-breaking space could help:
Conclusion
There is a chance I explained something incorrectly. Still, the general idea should be clear: when working with a flex-basis
, it does not have any impact on the intrinsic size of the flex container, so we can first size things based on the regular dimensions, and then adjust them.
When I first encountered this, I treated it as a bug, until I noticed that this behavior is consistent across all browsers, and seems properly specified.
It was fun finding an interesting use case for this behavior and applying it there. There are still areas of the flex algorithm that I do not understand completely, and I will need to return to it and explore it more.
And if you find other curious use cases for this flex-basis
behavior in tandem with intrinsic sizing — please, let me know!