How to unfuck CSS flex — The basics
UIScript DevBlog, Part 14
Do you find flexbox confusing?
Have you tried checking the official specification? Or Stackoverflow?
How about a clever cheat sheet? Or a fun little game?
Nothing?
Then maybe this is your lucky day. What if I told you that the problem isn’t you or your lack of cognitive ability to understand and memorize a boatload of random concepts but the lack of cognitive ability on the part of the people who pulled these concepts out of their asses?
What if, instead of mnemonic devices (I mean, really?), we would redesign this model so that we wouldn’t need any of this junk just to use a f*cking layout model?
Wouldn’t that be great?
Well, this is exactly what I did.
Since there are only a handful of layout models provided by browsers, I really want to make this model work for UIScript. My idea is to wrap this model in JS and fix whatever properties and values I can to make it simple, intuitive and consistent both internally and externally.
There is a lot to unpack here and there is only so much I can put into a single article so I will gradually cover each aspect of this layout model in the coming weeks.
In this article I will delve into the basics: how to better name this model and how to better define its direction.
Note: If you aren’t interested in UIScript, don’t worry, this article might still be useful to you to decipher flexbox.
Let’s get started.
Introducing the Stack
Right off the bat, we need a better name. Flexbox is neither a box (in fact, there aren’t even any boxes in CSS), nor is it flexible in most cases (we’ll get there in another installment).
Instead, let’s call this model a Stack.
A Stack is a simple collection of ordered views that might be familiar to you if you are a Swiftie (no, not that kind of Swiftie, but the programmer kind), but even beyond SwiftUI and programming, stacks are essentially everywhere.
Here is one in action:
Stack(
Text("Hello"),
Text("World")
);
This syntax is also a much more direct approach. Instead of adding a layout mode to an element, the constructor itself defines the layout:
/* instead of this in CSS */
#flex-container {
display: flex;
}
//we do this in UIScript
Stack();
What can be more semantic than this?
Deranged directions
Our next stop is the direction of the distribution. This is where things go from bad to worse.
The problem with flex-direction
is that a row
is not a direction, neither is a column
. They are structures that don’t have a direction.
In fact, we don’t even know whether row
means “left to right” or “right to left” without consulting a separate property — direction
— that sets the direction of a whole different thing (text).
Never mind the fact that flex direction is called flex-direction
and text direction is called simply direction
, and the fact that flex direction uses completely different values than text direction, text direction doesn’t even have a ttb
or btt
value, so we still don’t know what column
actually means.
#left-to-right {
direction: ltr; /* sure thing */
}
#right-to-left {
direction: rtl; /* no problem */
}
#top-to-bottom {
direction: ttb; /* no such value, sowwy */
}
#bottom-to-top {
direction: btt; /* no such value either */
}
Is it “top to bottom” or “bottom to top”? Sure, we can consider the former the default, but are there any other properties affecting this? What about writing-mode
and/or text-orientation
?
Maybe, maybe not. Who knows?
But hey, did you know that we can also reverse them, even though it makes even less sense. What exactly does it mean to reverse a row? Or a column?
/* stranger things */
#mirror-mirror {
flex-direction: row-reverse;
}
#upside-down {
flex-direction: column-reverse;
}
Are you kidding me?
And then, we have the wrap
. Since CSS cannot comprehend that wrap settings belong to the overflow
property, we have yet another property called flex-wrap
that sets the direction of the wrap.
No, not flex-wrap-direction
, but simply flex-wrap
. You know, to make it easy to remember.
Now, I can understand wrapping rows of text, but what does it even mean to wrap a column? Or to reverse wrap a reversed column? 😵💫
I mean, it’s just diabolical.
I get it, you can write a thousand pretty specifications and redefine every single word in the English language, but that doesn’t make your junk, well, less junk.
At this point I could argue that even this would be better:
#your-present {
flex-wrap: wrap;
}
#my-present {
flex-wrap: unwrap;
}
Or this:
#yummy {
flex-wrap: 🌯;
}
#yucky {
flex-wrap: -🌯;
}
They sure save some bandwidth and typing time, and easy to spot, even in a large codebase. What can be more important than that?
Some fresh water in hell
Now, onto the solution.
Instead of new properties like flex-direction
and flex-wrap
, and new words like
- row
- column
- wrap
- reverse
Stacks in UIScript simply use the .direction()
and .overflow()
getter/setters with common words like
- left
- right
- top
- bottom
Here is a simple example:
Stack()
.direction("top bottom")
.overflowY("left right");
We don’t even need a third property for shorthand access, like flex-flow
, we can combine the two inside .direction()
:
Stack()
.direction("top bottom", "left right");
Note: Verbosity is up for debate: we could use “ltr”, “left right”, “left to right”, or “from left to right”, but the point is they are actual directions.
The most confusing part about flex is that it obfuscates the fact that there are exactly 8 different multiline/multicolumn layouts in total.
These are all the options using CSS:
#flexbox {
flex-flow: row wrap;
flex-flow: row wrap-reverse;
flex-flow: row-reverse wrap;
flex-flow: row-reverse wrap-reverse;
flex-flow: column wrap;
flex-flow: column wrap-reverse;
flex-flow: column-reverse wrap;
flex-flow: column-reverse wrap-reverse;
}
And these are all the options using UIScript:
Stack()
.direction("left right", "top bottom")
.direction("left right", "bottom top")
.direction("right left", "top bottom")
.direction("right left", "bottom top")
.direction("top bottom", "left right")
.direction("top bottom", "right left")
.direction("bottom top", "left right")
.direction("bottom top", "right left");
What’s even more interesting is once we make an exhaustive list like this, it becomes clear what text direction
and text wrap is missing, even though they shouldn’t:
#text {
direction: ltr ttb;
direction: ltr btt; /* no such option */
direction: rtl ttb;
direction: rtl btt; /* no such option */
direction: ttb ltr; /* no such option */
direction: ttb rtl; /* no such option */
direction: btt ltr; /* no such option */
direction: btt rtl; /* no such option */
}
Sure, some options like “bottom to top” text or “bottom to top” wrap may feel alien to you, but all of these are valid options nonetheless.
In fact, the designers of the direction
property clearly thought that even “top to bottom” text wasn’t a thing, even though major languages like Chinese, Japanese, Mongolian and Korean are still using it, let alone older languages and scripts that have to be supported too.
For a community that prides itself on accessibility and diversity, it sure feels ironic.
Why not just implement all possible options in a single, comprehensive model? Why does everything feel like an afterthought in CSS?
A quick word about defaults
Once we make a comprehensive list of all of our options, it becomes much easier to define defaults.
For example, we can still tie the direction of the basic Stack to the writing direction, but now it is much clearer:
//text is left to right
Stack(); //same as Stack().direction("left right")
//text is right to left
Stack(); //same as Stack().direction("right left")
//text is top to bottom
Stack(); //same as Stack().direction("top bottom");
//text is bottom to top
Stack(); //same as Stack().direction("bottom top");
In addition, we can introduce SwiftUI-style constructors like VStack()
and HStack()
that create “top to bottom” and “left to right” distributions by default, but ZStack is where flexbox absolutely shits the bed.
Just as with the text direction
, poor flexbox completely skipped over one third of all possible options.
But that’s a rage for another day.
HStack(); //same as Stack().direction("left right")
VStack(); //same as Stack().direction("top bottom")
ZStack(); //not possible using flex, under development using another model
That’s a wrap (pun unintended)
That’s all I have for you today. Thank you for making it to the very end and I hope you’ve found it interesting.
In the coming weeks I will discuss the rest of flexbox/Stacks, including alignments, margins, paddings, gaps, and sizes, so stay tuned.
Until then, if you like UIScript, please clap, comment and share my work with others.
Thank you.