Equal Height Holy Grail Layout in Modern CSS
Equal Height Holy Grail Layout in Modern CSS
I constantly forget all of the random steps necessary to make equal height columns so I'm writing this down and sharing it. Most of the information out there on how exactly to create the "holy grail" layout is fairly lacking in explaining why each setting works, or is too old and doesn't use flexbox. A lot of the existing documentation also glosses over everything that's actually needed, and just says "flexbox! yay!" This blog post attempts to explain everything used to make this style of layout, but if you find there could be improvements then please let me know.
TLDR
To create the "holy grail" layout you need to solve three things:
- Full height center sandwiched between a header and footer.
- The center has two or more columns that are equal height no matter what content they contain.
- Avoiding fixed heights on the center columns so content doesn't "explode" out.
With flexbox this becomes easier, but it is still more complex than usually explained. The actual requirements are:
- All blocks set to
display: flex
. <header>
and<footer>
set to avar(--height-footer)
andvar(--height-header)
variables to fix the height.- A
<main>
(grandparent) tag set toflex-direction: column
and a height calculation ofcalc(100vh - var(--height-header) - var(--height-footer))
. - A
<main><columns>
(parent) tag set toflex-direction: row
andflex: 1 1 auto
. - A
<main><columns><left>
and<right>
(child) set toflex: 1 1 auto
withdisplay: block
optional if you don't want its contents to flex. - Set the
:root{}
variables--height-header
and--height-footer
to whatever you want for them. - Reset
<body>
to havemargin: 0px
andpadding: 0px
so everything is full screen.
This will give you a starter "holy grail" layout that you can then adjust and alter depending on what you need.
What is "The Holy Grail"?
The "Holy Grail" comes from the period right after everyone on the internet decided that <table>
tags shall be henceforth banned by Edict of Zeldman. The layout is this:
Seems simple right? You have a header, usually with a navigation element, content with a left and right side (usually of different widths), and then a footer with a ton of links in it. The problem comes when you aren't allowed to use a <table>
and have to use only <div>
tags. You're then stuck using crazy CSS tricks that barely work, and ultimately fail when you switch to mobile.
Modern CSS lets you use flexbox
and grid
to create the holy grail layout more easily, and in this article I'll show you how to use flexbox
to do this. I'll use grid
in another blog post to compare the advantages/disadvantages. This description will also explain why this works so you (and I) can hopefully remember this in the future.
Caveats
Keep in mind this is a starter and not meant to be your entire layout done for you. It simply solves the key problem of getting a header, footer, main content, and two columns on the screen and filling it in a reliable way. You'll still have to do work if you want it to look different, but starting with this will get you farther than trying to figure it all out on your own.
HTML Headers
The simplest HTML headers to get started are:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset='utf-8'>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta content='text/html; charset=UTF-8' http-equiv='Content-Type' />
<title>Holy Grail Starter</title>
<style>
/* CSS goes here. When you see me describe CSS you put it here. */
</style>
</head>
<!-- body here -->
</html>
The Body, Header, Footer
Your next step is to create the first body, header, and footer tags:
<body>
<header>
<h1>Header</h1>
</header>
<!-- replace this with the next step -->
<footer>
<h1>Footer</h1>
</footer>
</body>
This needs to be modified with the following CSS:
:root {
--height-header: 80px;
--height-footer: 200px;
}
body {
margin: 0px;
padding: 0px;
}
footer {
display: flex;
background-color: hsl(0, 0%, 80%);
min-height: var(--height-footer);
}
header {
display: flex;
background-color: hsl(0, 0%, 80%);
height: var(--height-header);
}
In my CSS here I'm setting the <header>
and <footer>
to have background-color: hsl(0, 0%, 80%);
which makes them gray. Change this later when you figure out your design. You should also note the use of :root{}
variables --height-header:
and --height-footer
as they help with the height calculations later.
Main, Columns
Once you have your HTML up and running you can create the <main>
and <columns>
tags that are sandwiched between the <header>
and <footer>
:
<main>
<columns>
<!-- next step left and right go here -->
</columns>
</main>
Now comes the "trick" which is to set <main>
to use column
orientation, and then set <columns>
to row
orientation. Confused? You should be because column
and row
are almost entirely arbitrary, but I'll try to explain why this is going to work after you see the CSS:
main {
display: flex;
flex-direction: column;
min-height: calc(100vh - var(--height-header) - var(--height-footer));
}
main > columns {
display: flex;
flex-direction: row;
flex: 1 1 auto;
}
To understand what's going on you need to know how flexbox
orients things (which also explains why "row" and "column" is so confusing):
flex-direction
determines something called the "main axis" and "cross axis" (or dimension), which is the direction that elements are "stacked" or arranged.- The
flex-direction: column
means to set the main axis to go vertically, or "along a column" which is up and down. This stacks elements like a stack of books. - That makes
flex-direction: row
set the main axis along the row, or horizontally. This stacks elements like beads on an abacus. - The easiest way to remember this is to use the fake words "hoROWzontally" and "vertiCOLUMNly". If you want the
flex-direction
to go horizontally, then say "hoROWzontally" and you'll know to userow
. If you want it to go vertically then say "vertiCOLUMNly" and you'll know to usecolumn
. - The main axis is the direction that child elements are "stacked", and the direction their size flexes. This means that
column
stacks vertically (vertiCOLUMNly) like a normal HTML page, androw
stacks horizontally (hoROWzontally) like text in this sentence. - The cross axis is the direction that is the inverse of the chosen
flex-direction
, so if you choosecolumn
(vertiCOLUMNly) then the cross axis isrow
(hoROWzontally). - Flexbox guarantees that all blocks are stretched to fill the cross axis unless told otherwise. Mark this idea down as it's what makes our next step work.
- The
flex: 1 1 auto
property applies a "grow shrink basis" modifier to this child using the parent's flex-direction. I'm constantly tripped up and think thatflex:
says how I want children to be sized, but it goes on the child and determines how it will grow. You'll find whether a property is for a child or a parent is rarely mentioned in the flexbox specification. - By applying
flex: 1 1 auto
tomain > columns {
we're saying, "Expand<columns>
to fill the<main>
parent's main axis (vertiCOLUMNly)." - Since the
main {
rule is set toflex-direction: column
our<columns>
tag will now stretch to the bottom of main, and because it automatically stretches along the cross axis (horizontally) this makes it stretch both ways.
That's a complex breakdown, but the simplest explanation is that you need make the <columns>
element to stretch across both the vertical and horizontal dimensions (axis) of <main>
. Since flexbox will do this across one dimension by default, you only need to set <columns>
to flex: 1 1 auto
to make it also stretch across the other dimension.
flex
property is part of flexbox it doesn't need to be in a block set to display:flex
. I'm not sure if this is standard, but it did work in display:block
definitions so it must only require a parent set to display:flex
. This makes sense since there's certain points when you don't want to use flexbox, but you still want to size that element.The min-height
Math
In the previous section I had this seemingly complicated and potentially dubious math:
main {
min-height: calc(100vh - var(--height-header) - var(--height-footer));
}
This uses a fixed height <header>
and <footer>
to create a center <main>
that fills the remaining space. It does this by:
min-height
requires that main not shrink below this height.calc
is a function for calculating math.var
references the variables--height-header
and--height-footer
we set in:root{
above.100vh
means "100% of the viewport height" so if your browser window is 500px tall then100vh
becomes 500px.- Putting it all together it will set the
min-height
ofmain
to 100% of the browser window viewport minus the<header>
and<footer>
height.
Does this mean that if the contents are greater than this height you'll get the classic CSS "explode out of box" problem? Not that I've seen, but then again CSS will surprise you and one day, for some random reason, your content will explode out or weirdly overlap. This setting seems to work as follows:
- If the content is less than the screen height - (header + footer) then it will fill the screen and stretch the space inside
<columns>
to fill that vertical space. - If the screen height shrinks to below the height of the inner columns then it will add scrollbars and not explode out.
If you see this acting differently please let me know and hopefully someone can explain why exactly this works or doesn't work.
Fixed Size Causes Explode Out
When a block has a fixed height you'll see strange "exploding out" of the contents. Normally this is obvious since it happens inside blocks where you've set a min/max height to a specific value, but sometimes it's deceptively hidden. Add this to the main {
CSS block:
max-height: 300px;
After you refresh this should look normal, depending on your screen height. It's subtle, but shrink the height of your window and you should see the text of <footer>
float above the left column. If you scroll down you'll see that actually the entire <footer>
block is sliding under the main content as you shrink it down. Weirdly, this also happens long before you reach the 300px
setting, and actually happens when the contents of <left>
are reached.
Even though everyone says that "exploding out" of the box only happens when you set the size of that box, it seems that the browser will punish you for setting any size on anything. We set a min-height:
on main, so now the browser takes liberty to completely screw up your layout in an unexpected way by not exploding the contents of <left>
out. No, it...scrolls <footer>
under <main>
?
Another way to put this is, according to the "rules", setting a min-height:
on <main>
should cause the contents of <left>
to explode out. It's entirely evil and unexpected to have <footer>
slide under <main>
instead, but I'm sure some CSS aficionado will have a detailed explanation that still doesn't forgive this behavior.
Left, Right
Inside <columns>
you'll have <left>
and <right>
children that contain your actual content. Here's an abbreviated sample as an example:
<left>
<!-- put any amount of content here -->
</left>
<right>
<!-- try less content here to test different heights -->
</right>
If you stop here and refresh the file you'll see that these two columns fill the vertical space, but they don't stretch across the horizontal space. We can break down why this happens like this:
<columns>
is set toflex-direction: row
so its main axis is hoROWzontal leaving its cross axis as vertical.- Flexbox automatically stretches children along the cross axis (vertical), but by default compacts elements along the main axis (hoROWzontal).
- That means, vertically (cross) the
<left>
and<right>
will stretch, and hoROWzontally (main) it will compress because offlex-direction: row
.
To fix this we have to apply the same trick from <columns>
and add flex: 1 1 auto
to the <left>
and <right>
elements:
The CSS for this part is:
main > columns left,
main > columns right {
display: block;
flex: 1 1 auto;
}
Notice here that I'm adding display: block
to show that you don't need these elements to be display: flex
for the flex: 1 1 auto
setting to work. This lets you stop using flexbox but still get the flexbox layout technology. If you were to put another block inside <left>
or <right>
then you would want to set <left>
or <right>
to display: flex
so you can apply these same techniques again. You'll see this in the later section Two Tone Columns.
flex: 1 1 auto
is a combination of flex-grow
, flex-shrink
, and flex-basis
. These are too complicated for one blog post, so just remember the magic incantation of flex: 1 1 auto
when you want something to "fill the space." If you want one element to be smaller than the other, then either change "auto" to be a fixed size, or try changing the "1 1" ratios until it's what you want.I also add some color to the left and right so I can see it for debugging to make sure they are equal height.
main > columns left {
background-color: hsl(0, 0%, 100%);
}
main > columns right {
background-color: hsl(0, 0%, 60%);
}
The Result
When you're done you should have something like this:
Try messing with the width, height, and contents of each block. You should try to break this in different ways and then find solutions. I give a few in the next sections.
Smaller Left/Right
The current layout is doing an equal sized left and right, which isn't usually what people want. I started with this because it's easiest to get working first. If you want the <left>
side to be smaller than the <right>
side remove this rule:
main > columns left,
main > columns right {
display: block;
flex: 1 1 auto;
}
And change these two:
main > columns left {
display: block;
flex: 0 1 300px;
background-color: hsl(0, 0%, 100%);
}
main > columns right {
display: block;
flex: 3 1 100%;
background-color: hsl(0, 0%, 60%);
}
This adds the flex:
property to both so you can specify exactly how they should size hoROWzontally (see <columns>
). The flex: 0 1 300px
property says to not grow <left>
and give it a base size of 300px (width). The flex: 3 1 100%
on <right>
says to make the right side grow 3x and default basis of 100%.
This will mostly work, but it'll depend on what you put in the left side, and you might need to use the classic width:
property instead. If everything works it should look like this:
Two Tone Columns
Another common style is to have the <columns>
split in half with two "tones" or colors so the screen is divided exactly in half, but to have the contents inside a different width. You can pull this off by first adding your inner content:
<left>
<info>
<h1>Left Info</h1>
</info>
</left>
<right>
<info>
<h1>Right Info</h1>
</info>
</right>
Next we change the main > columns left
and main > columns right
rules to implement row-reverse
on the left, and row
on the right:
main > columns left {
display: flex;
flex-direction: row-reverse;
}
main > columns right {
display: flex;
flex-direction: row;
background-color: hsl(0, 0%, 60%);
}
Here's how this CSS works:
- If you remember that
flex-direction: row
stacks hoROWzontally thenrow-reverse
simply reverses the order of blocks. - Putting
row-reverse
on<left>
will cause that side's<info>
block to push to the right. That then makes the two<info>
elements sit next to each other in the center. - Then there's
background-color: hsl(0, 0%, 60%)
on the<right>
tag so it has a background that covers the entire right side of the screen.
Once that's working we can give the two inside <info>
elements a width using flex: 0 1 400px
:
main > columns left info {
flex: 0 1 400px;
border: 1px solid black;
}
main > columns right info {
flex: 0 1 400px;
border: 1px solid black;
}
This sets the <info>
elements to have a base size of 400px
, but they will shrink as needed (up to the content size...hopefully). The flex: 0 1 400px
says, 0 growth, 1 shrink, 400px base width.
I also have a border: 1px solid black
on these <info>
blocks so you can see them, but remove these if you use this. The end result of these changes is this:
Simple Mobile
Supporting mobile with the first layout requires only changing the direction of the <columns>
:
@media only screen and (max-width: 700px) {
main > columns {
flex-direction: column;
}
}
This says when the screen width dips below 700px
the browser should apply the rules inside { }
. In this code I'm simply changing the flex-direction:
from row
to column
which makes the <left>
and <right>
elements stack vertiCOLUMNly and that fits on a mobile screen.
If you wanted the <right>
element to be on top then you would use column-reverse
to reverse the new stack. Either way this is how is should end up:
Getting the Code
You can view all of the code in the site blog git where you can view the following files:
basic.html
html raw -- The first layout created, no mobile fix.twotone.html
html raw -- The "Two Tone" left and right side demo.mobile.html
html raw -- The simple first step for a mobile responsive layout.sidebar.html
html raw -- The demo with a small left sidebar.all.html
html raw -- All of the features in one file: left sidebar, two tones, and simple mobile.
More from Learn Code the Hard Way
Rogue is the Best Project
My pitch for Rogue being the best for both beginners and old crusty coders like me.
Very Deep Not Boring Beginner Projects
A list of projects that anyone with basic programming knowledge can do in any language, but have a lot of depth and aren't boring to do.
C++ Is An Absolute Blast
Where I try to explain why I feel like C\+\+ is so much fun, and to correct some misinformation.
Just Use MSYS2 You Say?
Debunking idiots on HackerNews who think a total beginner can just 'Use MSYS2' on Windows.