It's possible that if you started making websites in the early 2000s, you made a website layout with a table. You should probably not do that anymore. It's possible that if you made a website anytime before 2015, you made it with a float layout structure. You should probably stop doing that, as well. Tables are for tabular data representation, and floats are for text wrapping, and we now have better tools for layout in HTML and CSS.
Check out CSS-TRICKS for more complete guides on Grid and Flex-Box.
So which one of these tools should you use? It's important to know the difference. Grid is like the great big daddy that can technically do anything, but it's way overly complicated for some things, or at the least, it's very complicated to learn the simple things it can do. On the other hand, flex-box can solve tons of simple problems all on its own, but once a layout gets too exacting it won't quite be enough.
Flex is good for this pattern. Rows of potentially uneven data.
Flex is also fine for patterns like this. Rows of probably even data.
Grid can also handle even row patterns.
Grid really shines with flat, big, predefined layouts.
This tutorial is going to heavily favor atomic classes. What are atomic classes? They are single purpose classes, most often named for exactly what they do. Most often only changing one value, these classes can build up on an element if left unchecked. Often times, however, elements only need one or two small value changes, and having single classes for common changes can mean a lot of options. Of course, it can also means building up a library of things that need to be memorized.
Creating simple to use classes can simplify the bootstrapping process.
Making a flex parent will turn a group of block elements into a group of close inline-like elements.
.flex-parent { display:flex; }
div
div.flex-parent
<div class="flex-parent">
<div>Item</div>
<div>Item</div>
<div>Item</div>
</div>
Creating a flex child class will go on the immediate children of a flex-parent. They will now fill as much space as possible, or shrink to give room for others. Use the shorthand syntax which stands for flex-grow, flex-shrink, and flex-basis all in one.
.flex-child { flex: 1 1 auto; }
<div class="flex-parent">
<div class="flex-child">Item</div>
<div class="flex-child">Item</div>
<div class="flex-child">Item</div>
</div>
Adding in a flex none will give you the ability to mix and match children that stretch or not. The flex-child elements will grow and shrink at a similar pace, while the flex-none elements will remain whatever size they were set to.
.flex-none { flex: none; }
<div class="flex-parent">
<div class="flex-none">.flex-none</div>
<div class="flex-child">.flex-child</div>
<div class="flex-child">.flex-child</div>
</div>
<div class="flex-parent">
<div class="flex-none">.flex-none</div>
<div class="flex-child">.flex-child</div>
<div class="flex-none">.flex-none</div>
</div>
<div class="flex-parent">
<div class="flex-child">.flex-child</div>
<div class="flex-none">.flex-none</div>
</div>
By default, the items are aligned along the horizontal axis to the top. It can often be useful to align all the children to the center. Most often this makes the most sense when all aligned items share a common background color.
.flex-align-center { align-items: center; }
<div class="flex-parent flex-align-center">
<div class="flex-child">.flex-child<br>.flex-child</div>
<div class="flex-child">.flex-child</div>
</div>
<div class="flex-parent flex-align-center">
<div class="flex-child"><div style="font-size:2.5em;line-height:1em; padding:1rem 0">Header</div></div>
<div class="flex-none flex-parent">
<div class="flex-child" style="padding:0 0.5em;">Link</div>
<div class="flex-child" style="padding:0 0.5em;">Link</div>
<div class="flex-child" style="padding:0 0.5em;">Link</div>
</div>
</div>
You can also pull elements to the center of the horizontal plane as well.
.flex-justify-center { justify-content: center; }
<div class="flex-parent flex-justify-center">
<div class="flex-none">.flex-none</div>
<div class="flex-none">.flex-none</div>
<div class="flex-none">.flex-none</div>
</div>
By default, a flex row will not wrap it's content. it will just stretch out into the ether. Adding a flex wrap class is a great way of making content that will stay within borders.
.flex-wrap { flex-wrap:wrap; }
<div class="flex-parent flex-wrap">
<div class="flex-none">item</div>
<div class="flex-none">item</div>
<div class="flex-none">item</div>
<!-- etc ... -->
</div>
When you start with flex, you'll probably focus on its ability to make normally vertical content be horizontal. But it's use for vertical content can also be important, especially for app development.
.flex-vertical { flex-direction:column; }
<div class="flex-parent flex-vertical" style="height:10em">
<div class="flex-none">.flex-none</div>
<div class="flex-child">.flex-child</div>
<div class="flex-none">.flex-none</div>
</div>
Grid is a lot harder to make simple bootstrappy classes for, because each grid is more or less unique. Grid can be very complex, especially when you are first introduced to it, but once it's broken down, it does have some easy to follow uses. You can create some simple row classes, and even make a default holy grail layout for yourself.
Setting yourself up with a grid-parent makes it real simple to define an element as a grid, but doesn't particularly imbue the element with anything by itself.
.grid-parent { display:grid }
<div class="grid-parent">
<div>Item</div>
<div>Item</div>
<div>Item</div>
</div>
Grid can be used to make simple repeating rows. Make as many simple repeating designs and all the children of the grid parent will flow into lines.
.grid-wrap-2 { grid-template-columns: repeat(2, 1fr); }
.grid-wrap-3 { grid-template-columns: repeat(3, 1fr); }
.grid-wrap-4 { grid-template-columns: repeat(4, 1fr); }
.grid-wrap-5 { grid-template-columns: repeat(5, 1fr); }
/* etc ... */
.grid-wrap-12 { grid-template-columns: repeat(12, 1fr); }
.grid-wrap-2
<div class="grid-parent grid-wrap-2">
<div>Item</div>
<div>Item</div>
<div>Item<br>Item</div>
<div>Item</div>
<!-- etc... -->
</div>
.grid-wrap-3
<div class="grid-parent grid-wrap-3">
<div>Item</div>
<div>Item</div>
<div>Item<br>Item</div>
<div>Item</div>
<!-- etc... -->
</div>
.grid-wrap-4
<div class="grid-parent grid-wrap-4">
<div>Item</div>
<div>Item</div>
<div>Item<br>Item</div>
<div>Item</div>
<!-- etc... -->
</div>
.grid-wrap-5
<div class="grid-parent grid-wrap-5">
<div>Item</div>
<div>Item</div>
<div>Item<br>Item</div>
<div>Item</div>
<!-- etc... -->
</div>
If we just give ourselves a few extra classes with some grid span values, we can do some really interesting things.
.grid-col-1 { grid-column: span 1; }
.grid-col-2 { grid-column: span 2; }
.grid-col-3 { grid-column: span 3; }
.grid-col-4 { grid-column: span 4; }
.grid-col-5 { grid-column: span 5; }
/* etc ... */
.grid-row-1 { grid-row: span 1; }
.grid-row-2 { grid-row: span 2; }
.grid-row-3 { grid-row: span 3; }
.grid-row-4 { grid-row: span 4; }
.grid-row-5 { grid-row: span 5; }
/* etc ... */
<div class="grid-parent grid-wrap-5">
<div>Item</div>
<div class="grid-col-3">Item</div>
<div class="grid-row-2">Item<br>Item</div>
<div class="grid-row-2 grid-col-2">Item</div>
<div class="grid-col-2">Item</div>
<div>Item</div>
<div class="grid-col-2">Item</div>
<div class="grid-col-5">Item</div>
</div>
Rounding out the basics, we can add a couple grid gap classes to give us some flexibility in designing our grids with gutters in between elements.
[class*='grid-gap'] { --gap-size:5px; }
.grid-gap-sm { grid-gap:var(--gap-size); }
.grid-gap-md { grid-gap:calc(2 * var(--gap-size)); }
.grid-gap-lg { grid-gap:calc(4 * var(--gap-size)); }
<div class="grid-parent grid-wrap-4 grid-gap-md">
<div>Item</div>
<div>Item</div>
<div class="grid-row-2 grid-col-2">Item</div>
<div class="grid-col-2">Item</div>
<div>Item</div>
<div class="grid-col-2">Item</div>
<div>Item</div>
</div>
The holy grail layout was a concept that web designers struggled with for a decade. Grid is THE definitive solution to this design pattern. You can even define a relatively simple structure for it, and standardize a little bit of naming for any variations you might want to make.
.grid-holygrail {
grid-template-areas:
"header header header"
"side-left body side-right"
"footer footer footer";
grid-template-rows: 3em 1fr 3em;
grid-template-columns: 100px 1fr 100px;
}
.grid-header { grid-area: header; }
.grid-side-left { grid-area: side-left; }
.grid-side-right { grid-area: side-right; }
.grid-body { grid-area: body; }
.grid-footer { grid-area: footer; }
<div class="grid-parent grid-holygrail">
<div class="grid-header">Header</div>
<div class="grid-side-left">Side<br>Side<br>Side</div>
<div class="grid-side-right">Side<br>Side<br>Side</div>
<div class="grid-body">Body</div>
<div class="grid-footer">Footer</div>
</div>
You could of course, then make whatever variations on the theme that you want.
.grid-one-column-left {
grid-template-areas:
"header header header"
"side-left body body"
"footer footer footer";
}
.grid-one-column-right {
grid-template-areas:
"header header header"
"body body side-right"
"footer footer footer";
}
<div class="grid-parent grid-holygrail grid-one-column-right">
<div class="grid-header">Header</div>
<div class="grid-side-right">Side<br>Side<br>Side</div>
<div class="grid-body">Body</div>
<div class="grid-footer">Footer</div>
</div>
<div class="grid-parent grid-holygrail grid-one-column-left">
<div class="grid-header">Header</div>
<div class="grid-side-left">Side<br>Side<br>Side</div>
<div class="grid-body">Body</div>
<div class="grid-footer">Footer</div>
</div>