Skip to content

Instantly share code, notes, and snippets.

@mauroc8
Last active January 8, 2022 00:03
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mauroc8/a6623a698b0b52c91d5e7c16a5a79379 to your computer and use it in GitHub Desktop.
Save mauroc8/a6623a698b0b52c91d5e7c16a5a79379 to your computer and use it in GitHub Desktop.

Abstracting flexbox away

Flexbox is the newest and most powerful CSS layout algorithm that can be used in production in 2022. But its terminology is confusing and exposes implementation details.

I wrote this article trying to explain the abstractions I generally use to work with flexbox. The objective of these abstractions is to provide a layout system. I implement them as CSS classes.

Flex containers: rows and columns

Rows are containers that have its children side by side:

+ .row ------------ +
| +---+ +---+ +---+ |
| | A | | B | | C | |
| |   | |   | |   | |
| +---+ +---+ +---+ |
+ ----------------- +
.row {
  display: flex;
  flex-direction: row;
}

Columns are containers that have its children stacked below:

+ .column +
| +-----+ |
| | A   | |
| +-----+ |
| +-----+ |
| | B   | |
| +-----+ |
+ ------- +
.column {
  display: flex;
  flex-direction: column;
}

Making the distinction is very important because the semantics of some CSS properties change depending on it.

Spacing

The elements inside a flex container (a .row or a .column) can be separated by some whitespace.

+ .row.spacing-1----- +
| +---+  +---+  +---+ |
| | A |  | B |  | C | |
| |   |  |   |  |   | |
| +---+  +---+  +---+ |
+ ------------------- +

+ .row.spacing-2 -------- +
| +---+    +---+    +---+ |
| | A |    | B |    | C | |
| |   |    |   |    |   | |
| +---+    +---+    +---+ |
+ ----------------------- +
.spacing-1 {
  gap: 1rem;
}

.spacing-2 {
  gap: 2rem;
}

I typically don't write CSS for this. I use inline CSS generated from javascript, to be able to use any value.

I call this "spacing" instead of gap because it is less confusing.

I encourage you to use only "spacing" (gap) and padding. Avoid margin (it isn't strictly necessary).

Having uniform spacing in the parent is cleaner and more declarative than having margins in each children.

Children with different spacing

If you need different spacing in each child, you can nest containers or use empty HTML tags.

For example:

+ --------------------- +
| +---+  +---+    +---+ |
| | A |  | B |    | C | |
| |   |  |   |    |   | |
| +---+  +---+    +---+ |
+ --------------------- +

Nesting containers:

<div class="row spacing-1">
    <p>A</p>
    <div class="row spacing-2">
        <p>B</p>
        <p>C</p>
    </div>
</div>

With empty HTML tags:

<div class="row">
    <p>A</p>
    <span style="width: 1rem"></span>
    <p>B</p>
    <span style="width: 2rem"></span>
    <p>C</p>
</div>

Centering

We may want to center an element inside a row or a column.

Centering can be done horizontally (along the X axis) or vertically (along the Y axis) or both.

In the flexbox model, the container controls the alignment of its children. So, in order to center the children, we apply a property to the container.

Center a row's children

A row with its children centered along the x axis.

(In CSS terminology, this is the "main" or "primary" axis)

+ .row.center-x --- +
|    +---+ +---+    |
|    | A | | B |    |
|    |   | |   |    |
|    +---+ +---+    |
+ ----------------- +
.row.center-x {
  justify-content: center;
}

A row with its children centered along the y axis.

(In css terminology, this is the "cross" or "secondary" axis)

+ .row.center-y -- +
|       +---+       |
| +---+ | B | +---+ |
| | A | |   | | C | |
| +---+ |   | +---+ |
|       +---+       |
+ ----------------- +
.row.center-y {
  align-items: center;
}

Notice in the illustration that, by default, row's children will stretch their height to fill the row's height.

Setting the alignment along the y axis will disable this behavior.

Center a column's children

A column with its children centered along the x axis.

+ .column.center-x -- +
|        +---+        |
|        | A |        |
|        +---+        |
| +-----------------+ |
| | B               | |
| +-----------------+ |
|      +-------+      |
|      | C     |      |
|      +-------+      |
+ ------------------- +
.column.center-x {
  align-items: center;
}

In columns, setting the alignment along the x axis disables the stretching of children's width.

A column with its children centered along the y axis.

+ .column.center-y +
|                  |
|                  |
| +--------------+ |
| | A            | |
| +--------------+ |
| +--------------+ |
| | B            | |
| +--------------+ |
|                  |
|                  |
+ ---------------- +
.column.center-y {
  justify-content: center;
}

Push all children to a side

Another common alignment we might want to use is to push all children to some side.

For example:

+ .row.align-bottom +
|                   |
|       +---+       |
|       | B |       |
| +---+ |   | +---+ |
| | A | |   | | C | |
| +---+ +---+ +---+ |
+ ----------------- +

+ .column.align-bottom +
|                      |
|                      |
| +------------------+ |
| | A                | |
| +------------------+ |
| +------------------+ |
| | B                | |
| +------------------+ |
+ -------------------- +
.row.align-top {
    align-items: flex-start;
}

.row.align-right {
    justify-content: flex-end;
}

.row.align-bottom {
    align-items: flex-end;
}

.row.align-left {
    /* This is the default */
}
.column.align-top {
    /* This is the default */
}

.column.align-right {
    align-items: flex-end;
}

.column.align-bottom {
    justify-content: flex-end;
}

.column.align-left {
  align-items: flex-start;
}

Fill width and height

Flex grow in rows

In a row, we might want to align some items to the right.

+ .row ------------------ +
| +---+ +---------+ +---+ |
| | A | | B       | | C | |
| +---+ +---------+ +---+ |
+------------------------ +

This can be done with flex-grow: 1. It tells a child to fill the available horizontal space.

<div class="row" style="width: 100%">
    <p>A</p>
    <p style="flex-grow: 1">B</p>
    <p>C</p>
</div>

Notice that I added the width: 100% in the .row, otherwise the row may shrink to its content (depending on the parent) and the flex-grow would have no sense, because there wouldn't be any available space to grow into.

Flex grow in columns

In columns, flex-grow: 1 tells a child to fill the vertical space. It can be used to push an element to the bottom.

+ .column +
| +-----+ |
| | A   | |
| +-----+ |
| +-----+ |
| | B   | |
| |     | |
| |     | |
| |     | |
| |     | |
| |     | |
| +-----+ |
| +-----+ |
| | C   | |
| +-----+ |
+ ------- +
<div class="column" style="height: 100%">
    <p>A</p>
    <p style="flex-grow: 1">B</p>
    <p>C</p>
</div>

.fill-width and .fill-height

The human-friendly name I use for flex-grow is fill-width and fill-height. The metaphor is that the element will change its width or height trying to fill all available space.

.row > .fill-width { flex-grow: 1; }
*:not(.row) > .fill-width { width: 100%; }

.column > .fill-height: { flex-grow: 1; }
*:not(.column) > .fill-height { height: 100%; }

That way, the latter example can be written:

<div class="column fill-height">
    <p>A</p>
    <p class="fill-height">B</p>
    <p>C</p>
</div>

Wrap

In rows, you may want to "wrap" the children. This means that, when there's not available space, children will prefer wrapping to a new line instead of shrinking.

+ .row.wrap ------- +
| +---+ +--------+  |
| | A | | B      |  |
| +---+ +--------+  |
| +------+          |
| | C    |          |
| +------+          |
+ ----------------- +
.wrap {
    flex-wrap: wrap;
}

This can also be done in columns.

+ .column.wrap - +
| +---+   +----+ |
| | A |   | C  | |
| +---+   |    | |
| +-----+ +----+ |
| | B   |        |
| |     |        |
| |     |        |
| +-----+        |
|                |
+ -------------- +

Fixed width (or height)

Children of flex containers can ignore its width or height attributes, even when you use !important. This is by design, but can be disabled with flex-shrink: 0.

It is not a good idea to disable flex-shrink always. We only want to disable it in elements that shouldn't shrink, such as images and icons.

.row > .fixed-width {
    flex-shrink: 0;
}

.column > .fixed-height {
    flex-shrink: 0;
}

You can have a small win is if you can automatically add the classes fixed-width or fixed-height when changing the width or height of an element to a fixed size.

Other values of flex-grow and flex-shrink

There are other values of flex-grow and flex-shrink you can use.

I haven't found them useful in practice yet, so I didn't feel the need for helper classes. If you're curious, I think this article explains them really well.

Other values of justify-content

The justify-content property can have three other values that we haven't covered yet: space-between, space-around and space-evenly.

You can compare them (with nice illustrations) in this flexbox reference.

Of those, I only find space-between to be useful.

.space-between {
    justify-content: space-between;
}

space-around looks weird (I can't imagine a situation where I'd need it) and space-evenly can be implemented using space-between and adding empty <div>s at the beginning and end of the container.

Other values of align-items

The only other value of align-items that looks useful to me is align-items: baseline. It aligns items in a row along the text's baseline.

I doubt it makes any sense to use it in a column.

IE11 support

Of all flexbox properties we've seen, gap is the only one not supported in IE11. There are two common workarounds.

Using negative margins

A common way to do imitate gap's behaviour is to use padding in each child and negative margins in the container.

.spacing-1 {
    margin: -0.5rem;
}

.spacing-1 > * {
    margin: 0.5rem;
}

Using :first-child

If the flex container doesn't have flex-wrap: wrap, there's a different workaround that uses margins in each child except the first (or last):

.row.spacing-1 > *:not(:first-child) {
  margin-left: 1rem;
}

.column.spacing-1 > *:not(:first-child) {
  margin-top: 1rem;
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment