Flexbox's 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.
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.
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.
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>
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.
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.
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;
}
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;
}
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.
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>
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>
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 | |
| | | |
| | | |
| +-----+ |
| |
+ -------------- +
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
orfixed-height
when changing thewidth
orheight
of an element to a fixed size.
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.
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) andspace-evenly
can be implemented usingspace-between
and adding empty<div>
s at the beginning and end of the container.
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.
Of all flexbox properties we've seen, gap
is the only one not supported in IE11.
There are two common workarounds.
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;
}
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;
}