CSS flex isn’t flexible, here’s why

UIScript DevBlog, Part 15

Bence Meszaros
6 min readOct 14, 2024
The three sizing scenarios in a container

Intro

As promised, let’s talk about why flex isn’t actually flexible in most cases.

When we discuss flex, we tend to focus on many properties like the flex-flow, flex-wrap and flex-basis, but almost always forget about two very important properties: the width and height of the container.

Take a look at this well-known cheat sheet from CSS-Tricks. It only mentions width three times, and height only once, neither is about the size of the container.

When you introduce the width and height of the container into the mix, flex suddenly becomes much easier to digest.

Let’s see why.

Size, not flex

All containers, including flex, have only three options to manage sizes:

  1. set the size of the parent and the size of the children independently
  2. derive the size of the parent from the size of the children
  3. derive the size of the children from the size of the parent

Note: The 4th option would be when we derive the size of the parent from the children, while also deriving the size of the children from the parent, which is impossible.

According to CSS Flexible Box Layout Module Level 1, § 7. Flexibility “the defining aspect of flex layout is the ability to make the flex items “flex”, altering their width/height to fill the available space in the main dimension”.

This statement is completely false, for several reasons:

  • The defining aspect of this layout model is not that the children “flex”, but that they are distributed along an axis (either horizontally or vertically)
  • Children only “flex” in one out of three scenarios
  • The parent can also “flex”, but again, only in a single scenario
  • We always care about both dimensions, not just the main

Instead of focusing on the “flex” aspect, I suggest that you focus on the sizing aspect. Use these 3 options as the basis of your mental model and always start by picking the one that fits your use case whenever you need a distribution in your layout.

Let’s see some actual examples.

1. Independent sizes

In this scenario, we set the size of the parent and the size of each children independently. This is useful for example for a carousel or a scrollable list:

Horizontal and vertical scrollable Stacks

This model is quite literally everywhere. This is the macOS System Settings with two vertical Stacks:

macOS System Settings

The key thing here is that if we add/remove children or shrink/grow them, they will not affect the size of their parent. Excessive children will simply overflow the given size and we can crop or scroll them.

Even though neither the parent nor its children are flexible, the layout is still responsive and automatic layout changes are fully under our control.

In UIScript, this would look something like this:

HStack(
View().width(50), //explicit
View().width(50), //explicit
View().width(50) //explicit
)
.width(100); //explicit

2. Parent size from children sizes

In this scenario, we derive the size of the parent from the size of its children. This is useful for example for vertical or horizontal tab bars:

Horizontal and vertical tab bars from Stacks

But the Dock in macOS also works in a similar way:

macOS Dock as a horizontal Stack

The key thing here is that if we add/remove children or shrink/grow them, the parent will shrink/grow as well. The children do not “flex”.

This is also an easy scenario, because making smaller sizes add up to a bigger size is usually a straightforward process.

In UIScript, this would look something like this:

HStack(
View().width(50), //explicit
View().width(50), //explicit
View().width(50) //explicit
)
.width(children.width); //implicit

3. Children sizes from parent size

In this scenario, we derive the sizes of children from the size of their parent.

This is where the flexibility of children comes into play and this was also the most touted feature of this model, because people saw this as the ultimate layout solution after the failures of flow-based and table-based approaches.

However, this isn’t some ultimate solution either, it is merely one special case of the distribution model.

A good example for this scenario is when we need multiple columns of text to fill the available space equally:

Multiple text blocks fill the available width

But of course, the distribution doesn’t have to be equal. Here is an example with two columns and a sidebar:

Sidebar and text blocks fill the available width

The problem is, deriving the size of children from the size of the parent is the hardest scenario, because unlike adding things up, dividing something up can happen in virtually endless ways, with various consequences that we need to address.

The complexity of this scenario is well beyond the scope of this article, but I’ll try to touch on this in future articles.

In UIScript, this would look something like this:

HStack(
View().width(parent.width / 3), //implicit
View().width(parent.width / 3), //implicit
View().width(parent.width / 3) //implicit
)
.width(1000); //explicit

Necessary complexity

There are several factors that naturally complicate sizing in containers:

  • We have only 3 scenarios but we need to consider them in both directions (horizontally and vertically), so in reality we are working with 9 different scenarios.
  • We can (or should be able to) control the sizes of children centrally, from the parent, regardless of scenarios or from individual children directly.
  • We can nest containers inside each other, making it quite complex to reconcile a top-down sizing (child size from parent size) with a bottom-up sizing (parent size from child size).

Unnecessary complexity

Unfortunately, there are several issues that further and unnecessarily complicate sizing in CSS flex:

  • Sizing is mixed up with positioning in the align-items and align-content properties. Both of these properties should be purely about positioning, they should not alter the size of any children in any way, yet, the stretch value does exactly that.
  • There is no stretch value for justify-content, making vertical and horizontal sizing inconsistent.
  • There aren’t any properties on the parent to control the width and height of all children centrally. There should be a concept similar to grid-template-rows and grid-template-columns.
  • Sizing on the children is also extremely chaotic: we can use the width and height properties (even with relative units), but there are also properties like flex-grow, flex-shrink, and flex-basis that greatly convolute them.

In an upcoming article I will discuss why these issues are severe and propose a much better design.

Conclusion

The CSS flex layout model is not about flexibility, but distribution.

Instead of flexibility, we should focus on sizing.

We can set sizes in a container in three ways:

  • the parent and children are independent
  • the size of the parent is derived from its children, or
  • the size of the children are derived from the parent.

Once we use this as the basis of our mental model, creating distributions will become much easier.

There are factors that naturally complicate this model, such as horizontal and vertical sizing at the same time, different control possibilities and container nesting.

There are also factors that unnecessarily complicate this model, such as inconsistent properties, mixed up values and bad terminology.

The goal of UIScript is to ease both, as much as possible. In the coming weeks I will discuss a better design of this model as part of UIScript.

If you like what I’m doing, please clap, comment and share my work with others.

Thank you and stay tuned.

⬅️ Part 14 — How to unfuck CSS flex — The basics

--

--

Bence Meszaros
Bence Meszaros

Written by Bence Meszaros

Lead Software Engineer, Fillun & Decketts

No responses yet