Holy Grail – just 3-column layout

The Holy Grail - is a CSS programming trick for dividing a web page into three columns.

The Holy Grail in web design refers to a web page layout which has multiple, equal height columns that are defined with style sheets. It is commonly desired and implemented, although the ways in which it can be implemented with current technologies all have drawbacks. Because of this, finding an optimal implementation has been likened to searching for the elusive Holy Grail.

The limitations of CSS and HTML, the desirability of semantically meaningful pages that rank well in search engines, and the deficiencies of various browsers combine to create a situation in which there is no way to create this type of layout that would be considered totally correct. As the underlying technologies do not provide a proper solution, web designers have found various ways to work around the limitations. Common workarounds include changes in page structure, the addition of graphics, scripting, and the creative use of CSS. These methods are imperfect, inconvenient, and may even be considered abuse of the web standards and their intent.

Upcoming web standards will deal with this and other layout issues in a much more elegant fashion. However, the problem will continue until these standards are finalized and widely implemented.

Contents:
1 What’s the problem?
2 Old solutions
    2.1 Tables
    2.2 Faux columns
    2.3 JavaScript
    2.4 Fixed positioning
    2.5 Nested divisions
    2.6 Border color
    2.7 Bottom padding
3 Current solution: Flexbox

What’s the problem?

Many web pages require a layout with multiple (often three) columns, with the main page content in one column (often the center), and supplementary content such as menus and advertisements in the other columns (sidebars). These columns commonly require separate backgrounds, with borders between them, and should appear to be the same height no matter which column has the tallest content. Another common requirement is that, when a page does not contain enough content to fill the screen, the footer should drop to the bottom of the browser window instead of leaving blank space underneath.

The problem with equal height columns

There are many obstacles to accomplishing this:

  • CSS, although quite useful for styling, currently has limited capabilities for page layout;
  • The height of block elements (such as div elements) is normally determined by their content. So two divisions, side by side, with different amounts of content, will have different heights unless their height is somehow set to an appropriate value;
  • HTML is meant to be used semantically. The purpose of HTML tags is to describe the type of content inside them. The appearance of a web page as rendered by a user agent should be determined independently by style rules. Many implementations misuse HTML by using tables for non-tabular data, or nesting multiple div tags without semantic purpose. Non-semantic use of HTML confuses users or user agents who are trying to discern the structure of the page content, and it is also an accessibility issue;
  • As search engines consider content in the beginning of a web page’s source code to be more relevant, web designers desire the ability to place the content in the page source in an arbitrary order, without affecting the appearance of the page;
  • Because of incorrect rendering of content by different browsers, a method that works in a standards-compliant browser may not work in one that does not implement CSS correctly.

Old solutions to find Holy Grail

Tables

Before the widespread implementation of CSS, tables were commonly used to layout pages. Sometimes the layout required several tables to be nested inside each other. Although placing the columns inside table cells easily achieves the desired visual appearance, using a table is semantically incorrect (although the “role” WAI-ARIA HTML attribute can be set to “presentation” to regain semantic context). There is also no way to control the order of the columns in the page source.

Faux columns

This method uses a background image which provides the background colors and vertical borders of all three columns. The content of each column is enclosed in a division, and positioned over its background using techniques such as floats, negative margins, and relative positioning. The background is normally only a few pixels high, and is made to cover the page using the “repeat-y” attribute. This works fine for fixed-width layouts, and can be adapted for percentage-based variable-width pages, but cannot be used for fluid center pages.

JavaScript

In this method, after the page is loaded, a script measures the height of each of the columns, and sets the height of each column to the greater value. This will not work in browsers that do not support JavaScript, or have JavaScript disabled.

Fixed or absolute CSS positioning

In this method, the corners of the column divisions are locked in a specific place on the page. This may be acceptable or even desired, but does not solve the Holy Grail problem as it is a significantly different layout. The consequences of this method may include having content appearing below the columns (such as a footer) fixed at the screen bottom, blank space under the column content, and requiring scrollbars for each column to view all the content.

Three divisions on the HTML page for absolute CSS positioning

So, such layout uses CSS absolute positioning. If we define 3 divisions on the HTML page:

  • div id="main"
  • div id="list1" class="link-list"
  • div id="list2" class="link-list"

we can apply then the following CSS rules on them:

/* Properties that both side lists have in common */
div.link-list {
        width:10.2em;
        position:absolute;
        top:0;
        font-size:80%;
        padding-left:1%;
        padding-right:1%;
        margin-left:0;
        margin-right:0;
}

/* we leave some place on the side using the margin-* properties */
#main {
        margin-left:10.2em;
        margin-right:10.2em;
        padding-left:1em;
        padding-right:1em;
}

/* and then we put each list on its place */
#list1 {left:0;}
#list2 {right:0;}

The idea is to crop the main division on the sides using the margin-left and margin-right properties, and then to position each side columns using position:absolute, and set the top left corner and top right corner coordinate to (0,0).

It’s ideal for homepages since it allows to have a complete text in the center and nice lists of links on the side. One of the restriction is that it won’t work for too bad CSS implementations, but it will degrade nicely into a traditional vertical layout.

Nested divisions

Since a division will grow in height to contain its content, if this containing division is assigned a background, the background will be as tall as the content. This behavior is used to solve the problem by creating three divisions nested inside each other which provide the three backgrounds. These divisions are placed in their proper location using positioning techniques, and the three content divisions are placed inside the innermost background division, and positioned. The background divisions become as tall as the tallest content division. The drawbacks of this method include the three non-semantic divisions, and the difficulty of positioning the elements of this complex layout.

Three column nested containers

Here is the HTML div structure:

<div id="container3">
    <div id="container2">
        <div id="container1">
            <div id="col1">Column 1</div>
            <div id="col2">Column 2</div>
            <div id="col3">Column 3</div>
        </div>
    </div>
</div>

And the complete CSS with padding:

#container3 {
    float: left;
    width: 100%;
    background: green;
    overflow: hidden;
    position: relative;
}

#container2 {
    float: left;
    width: 100%;
    background: yellow;
    position: relative;
    right: 30%;
}

#container1 {
    float: left;
    width: 100%;
    background: red;
    position: relative;
    right: 40%;
}

#col1 {
    float: left;
    width: 26%;
    position: relative;
    left: 72%;
    overflow: hidden;
}

#col2 {
    float: left;
    width: 36%;
    position: relative;
    left: 76%;
    overflow: hidden;
}

#col3 {
    float: left;
    width: 26%;
    position: relative;
    left: 80%;
    overflow: hidden;
}

Border color

A simpler version of the nested division method entails using a single container division. The background properties of this division provides the background of the center column, and the left and right borders, which are given widths equal to the side column widths, provide the background colors of the sidebars. The content of each column is positioned over its background. This method still uses one non-semantic division, and makes it difficult to apply background images and borders to the sidebars.

The HTML:

<div id="container">
   <div id="center">Center Column Content</div>
   <div id="leftRail">Left Sidebar</div>
   <div id="rightRail">Right Sidebar</div>
 </div>

The CSS:

body{
  margin:0 100px;
  padding:0 200px 0 150px;
}

#container{
  background-color:#0ff;
  float:left;
  width:100%; 
  border-left:150px solid #0f0;
  border-right:200px solid #f00;
  margin-left:-150px;
  margin-right:-200px;
  display:inline; /* So IE plays nice */
}

#leftRail{
  float:left;
  width:150px;
  margin-left:-150px;
  position:relative;
}

#center{
  float:left;
  width:100%;
  margin-right:-100%;
}

#rightRail{
  float:right;
  width:200px;
  margin-right:-200px;
  position:relative;
}

Bottom padding

By placing a large amount of padding at the bottom of the column container, the background will extend far below the column content. A corresponding negative margin will bring content below the columns back into its proper position. Positioning is simple in this method, as the container of a column’s content also contains its background. A padding value of 32767px is the largest that will be recognized by all modern browsers. If the difference in column heights is greater than this, the background of the shorter column will not fully fill the column.

By placing a large amount of padding at the bottom of the column container, the background will extend far below the column content.

The basic method works like this:

  1. Blocks which will act as columns must be wrapped in a container element;
  2. Apply overflow: hidden to the container element;
  3. Apply padding-bottom: $big_value to the column blocks, where $big_value is a large enough value to guarantee that it’s equal to or larger than the tallest column;
  4. Apply margin-bottom: -$big_value to the column blocks.
#block_1, #block_2, #block_3 {
	padding-bottom: 32767px;
	margin-bottom: -32767px;
}

#wrapper {
	overflow: hidden;
}

Current Holy Grail solution: CSS3 Flexible Box Layout (Flexbox)

The W3C has approached the layout issue through various proposals. The most mature proposal is the Flexible Box Module (A.K.A. Flexbox), which is in Candidate Recommendation status as of 26 May 2016. Setting an element’s display property to “flex” or “inline-flex” causes the element to become a new type of container (similar to a block or inline block, respectively), with new methods of positioning child objects. Content can flow in any direction, and be displayed in any order. The W3C proposal contains an example which achieves the Holy Grail column layout using four simple CSS rules, and makes the layout responsive with a simple media query rule. The module can also be used to address many other layout issues.

The Flexible Box Layout Module is supported in all of the modern browsers. Older browsers still have issues, but most of those (mainly Internet Explorer 9 and below) are no longer supported by their vendors. Many designers will choose to provide compatible styling for older browsers, to be overridden in modern browsers by the new syntax. In spite of the continued existence of legacy browsers, this module is currently touted as a solution to the Holy Grail issue.

The HTML:

<body>
<nav>
  <h3>Navigation</h3>
  <ul>
	<li>Item 1</li>
	<li>Item 2</li>
	<li>Item 3</li>       
  </ul>
</nav>
<section>
  <h1>Search for the Holy Grail</h1>
  <p>Lorem ipsum dolor sit amet.</p>
</section>
<aside>
  <h3>Advertisements</h3>
</aside>
</body>

The Flexbox CSS:

html, body {
  width: 100%;
  height: 100%;
  margin: 0;
  font-family: sans-serif;
}

body {
  display: flex;
}

nav, aside {
  flex: 0 0 12em;
  background: #ccc;
  padding: 1em;
}

section {
  background: #ddd;
  padding: 2em;
}