Logo
Logo

Programming by Stealth

A blog and podcast series by Bart Busschots & Allison Sheridan.

PBS 57 of X — The Bootstrap Grid

As we’ve learned in previous instalments, there are four distinct aspects to Bootstrap, a collection of utility CSS classes, a collection of styles for controlling built-in HTML elements like headers, figures, images, and tables (which Bootstrap refers to as content), page layout functionality, and a collection of reusable components that don’t exist in native HTML. We started by looking at the utilities, then moved on to styling the standard HTML elements. Now we’re ready for our first look at layout.

Bootstrap’s layout functionality is designed from the ground up to be responsive, that is to say, to allow you to control the layout of a page differently depending on the size of the viewport. For example, you can create a single layout that shows as a simple single column when viewed on a small phone screen, two columns on a tablet, and three on a device with a larger screen.

Going from zero to responsive design would be a big leap, so we’re going to break it down into two distinct parts. In this instalment we’re going to confine ourselves to creating layouts that work on larger screen devices like desktops, laptops, and large tablets. Once we can lay things out at one size, then we’ll add in responsiveness in the following instalment.

You can download this instalment’s ZIP file here or here on GitHub.

Matching Podcast Episode 553

Listen along to this instalment on episode 553 of the Chit Chat Across the Pond Podcast

You can also Download the MP3

PBS 56 Challenge Solution

The challenge set at the end of the previous instalment involved continuing to improve the recipe we’ve been working on for the past few challenges.

The first part of the challenge was to make the image in your recipe responsive. That’s to say, make it shrink automatically when the viewport becomes smaller than the image. Bootstrap makes this very simple — just add the class img-fluid to the <img> tag.

The second part was a little more challenging — opt the figure into Bootstrap’s more opinionated styling, and format it in a way that makes sense within your page. Opting the figure into Bootstrap’s styles involves adding the class figure to the <figure> tag, the class figure-img to the <img> tag within the figure, and the class figure-caption to the <figcaption> tag:

<figure class="text-center figure">
    <img class="border rounded img-fluid figure-img" alt="Roasted Cucumber with Red Onion &amp; Dill" src="roastedCucumberWithRedOnionAndDill.png" />
    <figcaption class="figure-caption">The finished product fresh out of the oven!</figcaption>
</figure>

Before opting into the Bootstrap styling by adding the class figure to the <figure> tag, my figure was centred, now it isn’t — why? As we learned in the previous instalment, it’s because the Bootstrap class figure sets the tag’s display property to inline-block. To fix this regression I simply had to force the figure to be displayed as a block by adding the Bootstrap utility class d-block:

<figure class="text-center figure d-block">
    <img class="border rounded img-fluid figure-img" alt="Roasted Cucumber with Red Onion &amp; Dill" src="roastedCucumberWithRedOnionAndDill.png" />
    <figcaption class="figure-caption">The finished product fresh out of the oven!</figcaption>
</figure>

The third task was to style the table of ingredients. The first step is to opt the table into Bootstrap’s opinionated styles by adding the class table to the <table> tag itself. Since my table is floated and 25% wide, I decided to opt for the more compact table style by also adding the class table-sm.

With the main body of the table now looking halfway decent, I turned my attention to the table heading. I had been using the Bootstrap utility class h2 to style my heading, but once I’d opted into Bootstrap’s opinionated tables, that wasn’t necessary anymore, so I removed it. Next I chose to opt for a subtle background on the header by adding the class thead-light to the <thead> tag.

Purely on aesthetic grounds I chose to make the text in the entire table muted by adding the Bootstrap utility class text-muted to the <table> tag. Finally, I enabled the highlighting of the row being hovered over by adding the class table-hover to the <table> tag.

Lastly, the challenge suggested making any other changes you thought were appropriate.

You’ll find my full sample solution in the folder named pbs56-challengeSolution in the ZIP file for this instalment or here on GitHub.

Introducing Bootstrap Grid Layouts

As we learned back in instalments 8 & 9, manually creating robust page layouts with CSS involves quite a bit of effort. This is where Bootstrap’s layout grid can really save you a lot of time, energy, and frustration.

You can use Bootstrap grids to lay out parts of a page or a whole page. Multiple grids can be nested inside each other as needed.

The Grid Structure

Each grid is contained within a wrapping tag that Bootstrap refers to as the grid’s container. Each container then contains one or more grid rows, and each row contains one or more grid columns. It’s the columns that contain your content. So, the important thing to remember is that containers contain rows which contain columns which contain the page’s content.

In Bootstrap, 12 is the Magic Number

When it comes to Bootstrap grids, it’s all about 12. Every container is conceptually subdivided into exactly 12 equal-width atomic columns (as I call them). You specify the widths of actual columns in terms of the number of atomic columns it should span. For example, you could specify that your sidebar should take up 3 of the 12 atomic columns, and your main content the other 9. Or, if you have two sidebars, you could allocate three to each and then use the remaining six for your main content region.

Even though a row is always 12 atomic columns wide, that doesn’t mean it’s necessarily a mistake to add more than 12 atomic columns worth of width into a row. Columns will overflow onto a new line when their total width exceeds 12. While it might sound counterintuitive now, it won’t seem that way next time. The fact that rows can overflow is central to Bootstrap’s support for responsive design.

Containers

In theory any tag can act as a grid container, but in reality, you’ll mostly be using <div> tags. Remember that divs are semantically meaningless groupings of tags. That is to say, they are designed explicitly not to convey meaning, but instead to simply collect together parts of a page so they can be manipulated in some way as a unit.

There are two kinds of Bootstrap container — fixed-width containers and fluid containers.

The width of a fixed-width container is always one of a small predefined set of possible widths. Which width the grid will be depends on the width of the viewport. Philosophically there’s a width for phones, a width for tablets, and one for desktops. What this means in real terms is that the width of the grid doesn’t scale linearly as you resize your browser window. Instead it jumps in large increments each time a threshold is passed. Between thresholds there will be empty space evenly distributed to the left and right of the container. This is quite an old-fashioned approach, and not particularly in vogue at the moment.

On the other hand, fluid containers always stretch out to fill 100% of the available width.

To mark a tag as a fixed-width container, give it the class container. To mark one as a fluid container, give it the class container-fluid.

Note that all nested containers behave like fluid containers, regardless of whether they have the class container or container-fluid.

The following simple demo (pbs57a.html in the ZIP file or here on GitHub) illustrates the difference between how fixed and fluid containers respond to window width — play around with the window’s width to see how differently the two kinds of container respond.

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8" />

    <!-- Include Bootstrap 4 CSS -->
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">

    <title>PBS 57 A — Container Demo</title>
</head>
<body>
<div class="container my-3 p-3 border rounded bg-primary text-center">
    <div class="row">
        <div class="col-12">
            <code>.container</code>
        </div>
    </div>
</div>
<div class="container-fluid my-3 p-3 border rounded bg-secondary text-center">
    <div class="row">
        <div class="col-12">
            <code>.container-fluid</code>
        </div>
    </div>
</div>
</body>
</html>

Rows

As a general rule, the only thing a Bootstrap grid container can contain is Bootstrap grid rows. Or, put another way, all children of a Bootstrap grid container must usually be Bootstrap grid rows. There are rare edge cases where the Bootstrap documentation suggests omitting rows and including columns directly within a container, but those are the exception, not the rule.

To mark a tag as a Bootstrap grid row, simply give it the class row. You can use any tag you like as a grid row, but very often, precisely because they have no semantic meaning, you’ll end up using <div>s.

Columns

The only thing a Bootstrap grid row can contain is Bootstrap grid columns.

There are many classes to indicate that a given tag should act as a grid column. Thankfully, the naming convention is very sensible, and they all start with col.

Firstly, you can just use col; this gives you an auto-width column. Whatever space in a row is not taken up by columns with explicit widths will be evenly divided between the columns with automatic widths.

The classes col-1 through col-12 will give you columns of width one through twelve atomic columns.

The following would be a very common arrangement — two side bars with explicit widths, and the space between given over to a central main content area (you’ll find this file in this instalment’s ZIP file or here on GitHub as pbs57b.html):

<!DOCTYPE HTML>
<html>
<head>
    <meta charset="utf-8" />

    <!-- Include Bootstrap 4 CSS -->
    <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
    <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">

    <title>PBS 57 B — Three-Column Layout Demo</title>
</head>
<body>
<div class="container-fluid my-3 p-3 border rounded bg-secondary text-center">
    <div class="row">
        <div class="col-3 rounded bg-primary py-5">
            <code>.col-3</code>
        </div>
        <div class="col rounded bg-light py-5">
            <code>.col</code>
        </div>
        <div class="col-3 rounded bg-primary py-5">
            <code>.col-3</code>
        </div>
    </div>
</div>
</body>
</html>

Unfilled and Overfilled Rows

As mentioned above, you don’t have exactly 12 atomic columns worth of actual columns in any given row. You can have fewer or more. Adding more than 12 will cause columns to wrap, but the result may be that you have a total that adds up to less than 12 on both rows. Imagine you had a col-9 followed by a col-5. Since nine plus five is greater than 12, the col-5 would wrap, leaving you with two apparent rows (though still considered a single Bootstrap row), neither of which are full.

How should the empty space be dealt with? Since the Bootstrap grid is actually built using flex boxes under the hood, you control the empty space using the same Bootstrap Utility classes we learned about in the flex box section of instalment 54 (justify-content-between, justify-content-around, etc.).

Grid Gutters

By default, Bootstrap adds some spacing between rows and columns so text in adjacent grid cells doesn’t touch. This is usually what you want, but not always. Bootstrap refers to this grid spacing as gutters. They can be removed on a row-by-row basis by adding the class no-gutters to the tag that’s acting as the grid row.

Revisiting Our Bootstrap Template

When we first met Bootstrap back in instalment 52, I told you to wrap your Bootstrap code in the following snippet:

<div class="container-fluid mt-3">
  <div class="row">
    <div class="col-12">
      <!-- Your Code Here -->
    </div>
  </div>
</div>

At the time I didn’t explain what the code meant, but promised I would at some vague future time when we’d learned enough Bootstrap for the explanation to make sense. I hope you can see that we’ve now arrived at that vague future. What that snippet does is wrap your code in a fluid container with a top margin of 3 and a single row containing a single full-width column. Why do we need to wrap our code in a container? Simple, when using Bootstrap, the page body has no padding, so content touches the edges. The container gives the needed padding left and right. By default there’s no margin above a container. In the real world that’s usually what you want because banners or navigation bars that touch the top of the viewport are all the rage these days! However, for playing around, I felt a little room at the top of the page worked better, so I added the mt-3 utility class to add a medium-sized margin to the top of the container.

A Worked Example

As a practical worked example, let’s take a slightly expanded version of the file with the tables and figures we used in the previous instalment as our starting point, and lay it out. To give the page a more consistent look across computers, I changed the existing fluid container into a fixed-width one. You’ll find the full source code for the starting point in this instalment’s ZIP or here on GitHub as pbs57c.html.

To lay out a page you need to break it down into chunks of related content, and then move those chunks around. Sometimes those chunks will have semantic meaning, and sometimes they’re just convenient chunks of information. Each chunk needs to be wrapped in a tag. My advice would be to prefer semantic tags over <div>s whenever they make sense, but only ever if they make sense.

Let’s start with the really big-picture stuff. The page clearly has a heading; so let’s wrap that in the semantic <header> tag. Everything else on the page is part of the main content of the page; so let’s wrap that in a <main> tag.

Since this is a single article, the best semantic tag for describing the distinct regions within the content is <section>. Let’s create sections for each of the chunks of content that begin with an <h2> tag. Since these headings will now becomes top-level headings within a section rather than second level heading within the page, they need to be changed from <h2>s to <h1>s.

Finally, to give us a meaningful sidebar to work with, I moved the small table and the small figure into a separate grouping. I chose to mark them up as an aside and removed the utility tags to set their width and to float them.

A version of the file with all the semantic markup added can be found in the ZIP file or here on GitHub as pbs57d.html.

Now that our page has been broken into chunks, we can think about how we’d like to lay those chunks out.

I’ve decided that I’d like to have a full-width header section followed by two columns containing the textual sections of the page in a wide column on the left, and the small table and figure in a sidebar on the right.

Then I want to switch back to full-width for the detailed results section with its big map and large, detailed table of results.

Finally, I want to switch to two equal-width columns for the sources and further reading sections.

At the top level of the page structure, the page is effectively divided into two — the page heading and everything else. We want that as two rows each containing one full-width column. The first row will only contain the header; that’ll be easy.

On the other hand the second row’s single column will contain everything else; So we’ll need to nest a second grid inside that.

The nested grid will consist of three rows — one with a wide main content area and a right sidebar, one with a single full-width column, and one with two equal-width columns.

All this involves a lot of open-heart surgery on the page. I’ll describe it step-by-step below, but don’t worry if it sounds confusing. I’ve included the finished source code in the ZIP file!

So, before we start our open-heart surgery, our page contains one container which contains one row which contains one column.

We’ll convert this existing container into the outer container in our desired nested layout.

Let’s start by breaking the header and the main content into their own rows:

We can leave the <div> that’s acting as the container (the one with the class container) in place, and we can also leave the start of the existing <div> that’s acting as a row in place (the one with the class row). However, instead of letting it wrap almost the entire rest of the page, we should close it just after the closing </header> tag.

We can get rid of the original column <div> (the one with the class col-12) because we can use the <header> tag as the column by giving it the class col-12.

Next, we’ll convert the entire <main> tag into the second row of the outer grid by giving it the class row. Finally, we need to wrap all the content of main in another tag that we’ll mark as a full-width column by giving it the class col-12. Since this grouping has no semantic meaning, I chose to use a <div>.

It’s inside this full-width column that takes up the entire second row of the outer grid that we want to create our nested inner grid. Before we do that, let’s pause and take stock of where things stand now. This is the current big-picture structure of our page:

<div class="container mt-3">
  <div class="row">
    <header class="col-12">
      <h1 class="display-1">Ireland Votes <small class="text-muted">May 2018</small></h1>

      <p class="lead">On the 25<sup>th</sup> of May 2018 the people of Ireland voted in a referendum on whether or not to accept the 36<sup>th</sup> amendment to their constitution. <a href="https://en.wikipedia.org/wiki/Thirty-sixth_Amendment_of_the_Constitution_Bill_2018" target="_blank" rel="noopener">Read More ...</a></p>
    </header>
  </div>

  <main class="row">
    <div class="col-12">
      <!-- REST OF PAGE HERE -->
    </div>
  </main>
</div>

Let’s go ahead and create that inner container now.

This container has no semantic meaning. So again, we should use a <div> with the class container. By default, containers have a padding. This is usually desirable, but not in this specific situation. With the padding in place the contents of our inner grid will appear offset from the content contained in the outer grid. We don’t want that. We can very easily remove the unwanted padding by adding the Bootstrap utility class p-0 to the inner container.

We now have a container without a row, so let’s open another <div> with the class row directly inside the inner container.

We want the first row to encompass everything from the section headed The Amendment to the end of the aside containing the small table and figure. So we should close the above <div> between the <aside> tag and the following <section> tag.

We’ll be adding more rows later, but for now let’s step into the one we just created and add our two columns.

The first of the two columns will contain three sections. The second column will contain the aside that contains the small table and figure. As things stand the three sections are independent elements; so they’ll need to be grouped together. We do this by wrapping a <div> around them. We’ll use this <div> as the first column in the first row of the nested grid, so we need to give it the class col (so it uses all the room we don’t assign to the aside).

The table and figure are already grouped together in the aside; so we can just convert that to our second column by giving it the class col-4. (Feel free to experiment with other widths, and watch how the main content area shrinks and grows accordingly because it doesn’t have a specific width assigned.)

That’s the first row of the nested grid taken care of.

We now need to create a second row. We do that by wrapping the detailed results section with another <div> and giving it the class row. Next we’ll need a single full-width column inside that row. The existing <section> tag wraps all the content we want in this column; so we can use it as the column by giving it the class col-12.

Finally, we want to group the last two sections into a third row in our inner grid with two columns. We’ll wrap both of those sections in yet another <div> ad give it the class row. Finally, we can convert the existing sections into the two columns we want by giving them both the class col.

At this stage we’re almost done. We just need to do a little housekeeping in the right sidebar. The table caption is not centred; so we need to give the <caption> tag within the small table the class text-center. The figure is also not centred; so we need to give it the classes d-block, mx-auto, and text-center (the technique we learned in the previous instalment for centring figures within their containing block).

You’ll find the full source for the finished laid out page in the ZIP file or here on GitHub as pbs57e.html, but this is the basic structure of the final page:

<!DOCTYPE html>
<html>
<head>
  <meta name="generator" content="HTML Tidy for HTML5 for Balthisar Tidy on macOS, version 5.7.16">
  <title>PBS 57 Dummy Page</title>
  <meta charset="utf-8"><!-- Include Bootstrap 4 CSS -->
  <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.1/css/bootstrap.min.css" integrity="sha384-WskhaSGFgHYWDcbwN70/dfYBj47jz9qbsMId/iRN3ewGhXQFZCSftd1LZCfmhktB" crossorigin="anonymous">
</head>
<body>
  <div class="container mt-3"> <!-- open outer container-->
    <div class="row"> <!-- open 1st row in outer container-->
      <header class="col-12"> <!-- open only col in 1st row in outer container-->
        ...
      </header> <!-- close only col in 1st row in outer container-->
    </div> <!-- close 1st row in outer container-->

    <main class="row"> <!-- open 2nd row in outer container-->
      <div class="col-12"> <!-- open only col in 2nd row in outer container-->
        <div class="container p-0"> <!-- open inner container -->
          <div class="row"> <!-- open 1st row in inner container -->
            <div class="col"> <!-- open 1st col in 1st row of inner container -->
              ...
            </div> <!-- close 1st col in 1st row of inner container -->

            <aside class="col-4"> <!-- open 2nd col in 1st row of inner container -->
              ...
            </aside> <!-- close 2nd col in 1st row of inner container -->
          </div> <!-- close 1st row in inner container -->

          <section class="row"> <!-- open 2nd row in inner container -->
            <div class="col-12"> <!-- open only col in 2nd row of inner container -->
              ...
            </div> <!-- close only col in 2nd row of inner container -->
          </section> <!-- close 2nd row in inner container -->

          <div class="row"> <!-- open 3rd row in inner container -->
            <section class="col"> <!-- open 1st col in 3rd row of inner container -->
              ...
            </section> <!-- close 1st col in 3rd row of inner container -->

            <section class="col"> <!-- open 2nd col in 3rd row of inner container -->
              ...
            </section> <!-- close 2nd col in 3rd row of inner container -->
          </div> <!-- close 3rd row in inner container -->
        </div> <!-- close inner container -->
      </div> <!-- close only col in 2nd row in outer container-->
    </main> <!-- close 2nd row in outer container-->
  </div> <!-- close outer container-->
</body>
</html>

Challenge

Using the recipe you’ve been developing over the past few challenges as a starting point, use what we’ve learned today to lay it out in whatever way you think works best for viewing on a desktop or laptop screen. For now, don’t worry about what it looks like on smaller screens. Dealing with multiple screen-sizes is a challenge for another time 😉

Final Thoughts

Now that we’ve learned how to lay out section of a page on large screens, we’re ready to dive into responsive design next time.

Join the Community

Find us in the PBS channel on the Podfeet Slack.

Podfeet Slack