CSS Layout: Evolution Detailed by Example
I was at a meetup where a fellow attendee described a layout question he got in an interview. He drew a picture of the information given in the interview and was told that it required absolute positioning and would need to be responsive. I have my favorite layout techniques, but this made me realize that there are many ways to achieve a side-by-side layout.
The evolution of web page layout tracks the evolution of the CSS and HTML Specifications and Standards. The timeline below summarizes the specs and related code that have been available to developers for laying out the web page.
After a brief history to help understand how we got to this point, we’ll look at code for laying out a set of three photos using all of the techniques above:
- HTML table
- display: table
- float
- absolution positioning
- inline-block
- bootstrap grid system
- flexbox
- grid
History
In the late 1990s, web developers were not trying to layout web pages the way that a print page might be laid out. It was not a requirement that elements like images, tables, and text be located side by side. The early HTML specs were concerned with document sharing, so formatting elements focused on document requirements such as header, paragraph, and font specifications.
The image of the first web page reminds us of the original HTML requirements. The elements always lined up in a column, and there was no attempt to place them side by side.
As web design has become more complex in layout and interactivity, so have the specifications for HTML and CSS. HTML is viewed as defining the structure of the page and CSS should handle the presentation. HTML is evolving to make it more semantic and pages should rely less on generic elements like <div> and <span> and more on <section> and <article>.
Browser Wars
Microsoft’s IE6 dominated the browser world for the period 2001 to 2005. Mozilla released Firefox in 2004, and this was the beginning of a battle to determine which browser would attract the most users. Because browser applications are traditionally free to use, it wasn’t about collecting money, it was about controlling the direction of the specifications. Since the browser is where specifications are implemented, this dominance forced developers to create many workarounds (hacks) in order to execute the new designs that would work for all browsers. The “spacer gif” in conjunction with the HTML table was one of these hacks. The gif could be given a height and width to allow it to push other items around on the page.
Responsiveness
During this period, the mobile phone also evolved, and it became possible to view web pages on the phone. This reduced viewport size created challenges for web developers as they were used to working in a large area. Having mastered techniques for placing elements side by side, they now needed methods to stack them on top of each other for the smaller displays. They needed a way to detect the device type and render different layouts for different device sizes. This is what we now know as responsive design.
Bootstrap
Bootstrap, the framework created by Twitter in 2010, provided a grid system based on the 960 grid system. The grid system was coded in CSS and provided in Bootstrap simplified development. The developer no longer had to write and maintain a lot of CSS that dealt with side-by-side layouts. Bootstrap also included media queries so that responsive design could be easily implemented. There are many alternatives to Bootstrap for layout, including the Flex and Grid CSS display properties. Built With reports that of the top one million websites, 17.9% are using Bootstrap, so it's worth knowing how to use Bootstrap for layout.
Code
All layout techniques will focus on achieving a similar layout: 3 photos displayed horizontally with space between and around them. Underneath each image will be some text. A GitHub repository with all the code is available. Each technique will have a section of HTML code and a section of CSS code.
At the end of this smorgasbord of layout techniques, we’ll look for similarities and differences and answer the question — which should we be using?
HTML Table
The HTML provides a table element with two rows, one for the images and one for the text. Tables were a natural first choice in early web development as the model for any layout is Cartesian: we think of an area of two-dimensional space in terms of rows and columns.
<table class="flowers">
<tr>
<td>
<img class="pix" src="images/cherry_blossom.jpg" alt="cherries"> </td>
<td>
<img class="pix" src="images/cherry_blossom.jpg" alt="cherries"> </td>
<td>
<img class="pix" src="images/cherry_blossom.jpg" alt="cherries"> </td>
</tr>
<tr>
<td>
<h3>First Place</h3>
<p class="comment">This is my favorite flower.</p>
</td>
<td>
<h3>Second Place</h3>
<p class="comment">This is my favorite flower</p>
</td>
<td>
<h3>Third Place</h3>
<p class="comment">This is my favorite flower</p>
</td>
</tr>
</table>
The .flowers table is rendered with a border-collapse so there is a single border between cells. The default (border-separate) places two borders between cells, which can cause styling problems and create extra “space” that is especially undesirable for layout calculations. The pix class applies a width of 100% to the image tag, which causes the otherwise large image (1200 x 900 px) to fit within the table.
The page is not responsive. If you shrink the screen or render it on a device with a smaller viewport, the table goes into the overflow, and you can’t see it.
Semantic HTML tells us that this is not the appropriate element for a layout. Tables are supposed to display tabular information, like data. Screen readers rely on the semantics of HTML to inform their users of content, and placing this code on a page would confuse a screen reader.
.flowers {
table-layout: fixed;
border-collapse: collapse;
}
td {
padding: 20px 20px 0 20px;
}
.pix {
width: 100%;
}
h3 {
text-align: center;
}
.comment {
padding: 0 30px;
font-size: 2rem;
text-align: center;
}
CSS display: table
The display: table allows the developer to make a table out of block elements. It essentially places the data in the block elements that are classed with the names “row” and “cell” as if they were in an HTML table element where “row” maps to <tr> and “cell” maps to <td>.
<section class="container">
<div class="row">
<div class="cell">
<img src="images/cherry_blossom.jpg" alt="cherries">
</div>
<div class="cell">
<img src="images/cherry_blossom.jpg" alt="cherries">
</div>
<div class="cell">
<img src="images/cherry_blossom.jpg" alt="cherries">
</div>
</div>
<div class="row">
<div class="cell">
<h3>My Favorite Photo</h3>
</div>
<div class="cell">
<h3>My Favorite Photo</h3>
</div>
<div class="cell">
<h3>My Favorite Photo</h3>
</div>
</div>
</section>
The CSS for this example assigns the names “row” and “cell” to the div tags that contain the image and the text. The image width is again set to 100% so that the large images fit within the table. The display property allows for the values “table”, “table-row”, and “table-cell”. These property values cause the table to be rendered in a tabular form. Text is aligned using text-align: center as in the HTML table.
This layout looks very much like the HTML table layout above, but it has the advantage of being responsive. It is responsive because the content fits in the viewport, but only by shrinking it.
.container {
display: table;
}.row {
display: table-row;
}.cell {
display: table-cell;
padding: 20px;
text-align: center;
}
.cell img {
width: 100%;
}
CSS float and clear
The HTML for float uses block elements classed with the familiar “row” and “column” class names to indicate that the content should be laid out in a tabular format.
<div class="row">
<div class="column">
<img src="images/cherry_blossom.jpg" alt="cherries">
</div>
<div class="column">
<img src="images/cherry_blossom.jpg" alt="cherries">
</div>
<div class="column">
<img src="images/cherry_blossom.jpg" alt="cherries">
</div>
</div><div class="row">
<div class="column text">
<h3>Favorite Picture</h3>
</div>
<div class="column text">
<h3>Favorite Picture</h3>
</div>
<div class="column text">
<h3>Favorite Picture</h3>
</div>
</div>
The CSS float property was initially used to float text around images. Floating an inline block element or an image will affect the positions of elements around it and disrupt the flow of the page. Picture a fish tank with the fish toys not anchored to anything and just floating around inside. With the clear property, it is possible to terminate the float and return to the normal flow of the page. These properties were used to create complex layouts behind the row/column model used in the examples here. However, once again, the nature of the problem here lends itself to thinking in terms of row and column.
Note that if you place the float on the body of the page, you don’t have to clear it. Clearing floats became a point of discussion, and the technique shown here using the pseudo selector::after and placing empty content with a clear property is just one technique known as “clearfix”.
The width of the column is set with a percentage value, making the page responsive. It’s not as sophisticated a responsiveness as you could get with media queries, but the content doesn’t overflow; it just shrinks.
* {
box-sizing: border-box;
}
.column {
float: left;
width: 33.33%;
padding: 20px;
}
.column img{
width: 100%;
}
.row::after {
content: "";
clear: both;
}
.text {
text-align: center;
}
CSS position: absolute
The HTML structure for absolute positioning creates a container with 3 vertical structures equally spaced from one other classed as “one”, “two”, and “three”. There is no indication of a table model in the structure or style. I’ve included content before and after the .flower-container to help explain why height is set on the .flower-container.
<h4>Flow before the container</h4>
<div class="flower-container">
<div class="one">
<img src="images/cherry_blossom.jpg" alt="Cherries">
<p>This is my favorite.</p>
</div>
<div class="two">
<img src="images/cherry_blossom.jpg" alt="Cherries">
<p>This is my favorite.</p>
</div>
<div class="three">
<img src="images/cherry_blossom.jpg" alt="Cherries">
<p>This is my favorite.</p>
</div>
</div>
<p>Flow after the container</p>
This layout implements one of the “tricks” of CSS positioning: wrapping an absolutely valued container within a relatively valued container. When this makes sense, you will have mastered your understanding of static, relative, absolute, and fixed display properties. Static positioning is just the normal right-to-left, top-to-bottom flow that you might think of as the default flow. The difference between absolute and fixed positioning is that absolute places content relative to its parent, while fixed places content relative to the window. Relatively positioned items can flow after previous content or be positioned relative to their container. Basically display:relative provides a non-static parent for the display:absolute to render in. By placing absolutely positioned items within a relative container, the positions you specify for these items are relative to the container's position and not the window. When using this technique, it is important to set a height on the relatively positioned item if you want the content to flow after it.
All divs placed in the .flower-container are positioned absolutely with the top at the middle (50%) and the transformed up and to the left (-50%, -50%). The width (20%) will give the distance between the items in what looks like a margin, and then padding (20px) pulls them away from a box-shadowed edge. The effect of this is to look like a Polaroid photo. Each of the 3 photo items is assigned its own left position (25%, 50%, 75%) to be placed in consecutive horizontal positions.
With a percentage within the absolute positioning, this layout is responsive in that it shrinks rather than overflows at different viewport sizes.
.flower-container {
position: relative;
height: 300px;
}
.flower-container div {
position: absolute;
top: 50%;
transform: translate(-50%, -50%);
width: 20%;
padding: 20px;
text-align: center;
box-shadow: 0 0 30px rgba(0, 0, 0, 0.2);
}
.flower-container .one {
left: 25%;
}
div.two {
left: 50%;
}
div.three {
left: 75%;
}
.flower-container img {
width: 100%;
}
CSS display:inline-block
The HTML for the inline-block layout is similar to the display: absolute in that there is a container with 3 vertical structures. Sometimes, there is a question: why use a block element with an inline-block style rather than an inline element — or why not use a span instead of a div? The answer is usually that you want to be able to provide block element styles such as height. In this case, we’re just taking advantage of the fact that inline-block can be used to horizontally align containers that, because of the items they too contain, maybe block-level items.
<div class="container">
<div class="pic">
<img src="images/cherry_blossom.jpg">
<h3>This is my favorite picture</h3>
</div>
<div class="pic">
<img src="images/cherry_blossom.jpg">
<h3>This is my favorite picture</h3>
</div>
<div class="pic">
<img src="images/cherry_blossom.jpg">
<h3>This is my favorite picture</h3>
</div>
</div>
The 3 photos are contained in .container, which are styled with inline-block to arrange them horizontally. Additionally, we want them centered vertically. When working with block elements, you might use margin: auto to center, but because these are inline-block, the text-align: center is the choice that does the job. The width of the images is set using rem, which provides a size relative to the size of the root element. The value 25 for the width is subjective based on knowing that the inline-block elements will wrap and that the centering can provide right and left margins. The fact that wrapping and size is a relative amount makes this technique responsive in a way that previous layouts were not.
.container {
text-align: center;
}
.pic {
display: inline-block;
}
.pic img {
width: 25rem;
}
CSS Bootstrap Grid System
You need to install jquery, Bootstrap... js, and Bootstrap... css to use the Bootstrap Grid System and the thumbnail component I’m using for this demo.
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-boots<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.js" async defer></script><script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.1/js/bootstrap.js" async defer>
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.1.1/js/bootstrap.js" async defer></script>
Bootstrap provides a row, column model for laying out elements. The columns include viewport sizes (like -md-) at which wrapping will occur and values that must add up to 12 (like -4-) before wrapping occurs. The HTML below encodes the Bootstrap thumbnail, an interactive component allowing the user to click on the photo to see a larger image in a new tab. This accounts for the extra code. Essentially, the structure is similar to the display: table example above.
<div class="container">
<h2>Image Gallery</h2>
<div class="row">
<div class="col-md-4">
<div class="thumbnail pic">
<a class="pic" href="images/cherry_blossom.jpg" alt="Cherries" target="_blank">
<img class="pic" src="images/cherry_blossom.jpg" alt="Cherries">
</a>
<div class="caption">
<h3>Favorite Photo </h3>
<p>Lorem ipsum donec id elit non mi porta gravida at eget metus.</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<a class="pic" href="images/cherry_blossom.jpg" alt="Cherries" target="_blank">
<img class="pic" src="images/cherry_blossom.jpg" alt="Cherries">
</a>
<div class="caption">
<h3>Favorite Photo </h3>
<p>Lorem ipsum donec id elit non mi porta gravida at eget metus.</p>
</div>
</div>
</div>
<div class="col-md-4">
<div class="thumbnail">
<a class="pic" href="images/cherry_blossom.jpg" alt="Cherries" target="_blank">
<img class="pic" src="images/cherry_blossom.jpg" alt="Cherries">
</a>
<div class="caption">
<h3>Favorite Photo </h3>
<p>Lorem ipsum donec id elit non mi porta gravida at eget metus.</p>
</div>
</div>
</div>
</div>
</div>
Because we are using Bootstrap CSS classes, the CSS provided just forces our large photo to fit in its Bootstrap-configured container. Bootstrap provides full responsiveness with media queries.
.pic {
width: 100%;
}
CSS display:flexbox
Flexbox provides either horizontal or vertical layout, with the justify property helping with alignment on the layout axis and the align property helping with alignment on the non-layout property. Sometimes, developers will use CSS class names like row and column, but in general, the model is not tabular. The idea is that you will let your elements flow in one direction (horizontal) or the other (vertical), with horizontal being the default.
The HTML suggests two containers that each contain lists of items.
<ul class="flex-container">
<li class="flex-item">
<img src="images/cherry_blossom.jpg">
<h3 class="description">My Favorite flower.</h3>
</li>
<li class="flex-item">
<img src="images/cherry_blossom.jpg">
<h3 class="description">My Favorite flower.</h3>
</li>
<li class="flex-item">
<img src="images/cherry_blossom.jpg">
<h3 class="description">My Favorite flower.</h3>
</li>
</ul>
The CSS for this layout relies on the default horizontal flex-direction. For the horizontal axis, the justify property is set to space-around to “spread” the images out across the page but use any extra space for free space around the images. For the vertical axis, the align-content property with a value of flex-start is used to put any extra space at the end of the column. The idea is to make this layout responsive by just causing the flexed contains to stack on top of one another when there’s not enough room to place side by side.
The <ul> tag adds padding by default and is set to 0 for a layout container here.
The 1200x900 px image’s height must be set as there is nothing to constrain it whereas the horizontal alignment constrains the width.
This layout is responsive and wraps — images stack vertically — when viewed in smaller viewports.
ul.flex-container {
list-style-type: none;
display: flex;
justify-content: space-around;
flex-wrap: wrap;
align-content: flex-start;
}
ul.flex-container {
padding: 0;
}
ul.flex-container img {
height: 300px;
}
.description {
text-align: center;
}
CSD display: grid
The HTML structure for the CSS grid layout provides no hint that the structure will appear as a row/column table-like visual. If you’re a programmer who has worked with vector or array structures in which rows and columns can be specified as options, this will be a familiar way of dealing with data. The grid layout makes it easy to change the design with media queries.
For this application of the horizontal alignment of 3 photos followed by the horizontal alignment of text, we provide a structure that is a container with items.
The grid is the next step after flexbox in CSS evolution. Where flexbox focuses on a single dimension or axis, the grid is a 2-dimensional display property.
<div class="grid-container">
<div class="grid-item">
<img src="images/cherry_blossom.jpg" alt="Cherries">
<h3>My Favorite Picture</h3>
</div>
<div class="grid-item">
<img src="images/cherry_blossom.jpg" alt="Cherries">
<h3>My Favorite Picture</h3>
</div>
<div class="grid-item">
<img src="images/cherry_blossom.jpg" alt="Cherries">
<h3>My Favorite Picture</h3>
</div>
</div>
The CSS for the grid layout tells the browser to use display:grid and then with the grid-template-columns property lays out the 3 columns with the “auto” property. The media query provides a way to wrap the elements when the viewport shrinks.
This is a responsive layout.
.grid-container {
display: grid;
grid-column-gap: 10px;
grid-row-gap: 10px;
grid-template-columns: auto auto auto;
}@media screen and (max-width: 992px) {
.grid-container {
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr))
}
}
.grid-item {
padding: 20px;
font-size: 30px;
text-align: center;
}
.grid-item img {
width: 100%;
}
Compare and Choose a Layout Technique
As I’ve pointed out in describing each technique, there is a standard model upon which most of the side-by-side layout is based: the table. This makes sense because historically, HTML only provided one 2 dimensional element, namely <table>. The implementation of the row/column model seen in the table has moved from HTML to CSS naming and properties.
The most recently released CSS layout technique, display:grid, has a grid-templates-column property, which suggests that this model persists and is still useful.
The question remains: which technique should you use? Any website is likely to be accessed on a cell phone, which is the smallest viewport; therefore, the layout must be responsive. While most techniques will give a responsive design, some are only responsive to shrinking the display. There needs to be more. Proper responsiveness requires that the side-by-side items be stacked on top of each other when rendered on a small device. Bootstrap provides classes handled by media queries, and I offered a media query for the grid layout. The only layouts that will “verticalize” the horizontal layout without a media query are the display:inline-block and flexbox.
I suggest learning all of the techniques because learning how they work will teach you a lot about the nature and evolution of CSS. You may also be asked to code one of them in an interview. In practice, I would choose the newest CSS, which means the flexbox or grid layout, assuming that the newest is supported in all of the browsers your users are using. It’s interesting to note that you can still run the code for the older techniques in any browser. CSS can be slow to evolve but equally slow to deprecate.
Ultimately, think about the content that you are trying to render. If you’re writing CSS for a component or for your own framework that should work for a wide range of content, such as text, images, and video, make sure you haven’t locked yourself into a technique in which you are hard coding dimensions that won’t apply to any and all content. If you’re not trying to write a layout for an abstracted component, choose the easiest to maintain. Easy to maintain CSS is made up of styles that are modular, well-named, specific, and don’t involve undoing other styles.
If you know of other layout techniques, please share!