Adding a Bootstrap 3 layout to a Rails 4 project

In this article, we are going to build on the project that we started in Using Bootstrap 3 with Rails 4.

Outcome

Our goal at the end of this article is to have implemented a Bootstrap 3 layout for our Rails 4 project similar to the image below.

Outcome

As you can see, we want to create a layout with a navigation bar at the top and a footer at the bottom. The content in between these two page elements will be dynamic from page to page. On the root url, or home page, of our application we want to have the option to include a Bootstrap jumbotron. The jumbotron is the section of the page in the image above that has a grey background and a heading of Hello, world! and it is not something we want to display on any of the other pages.

Creating some static pages

To build the layout, we are going to need to create 5 static pages which are listed in the table below.

Name URL Route
About Us /about about_path
Contact Us /contact contact_path
Home / root_path
Privacy Policy /privacy privacy_path
Terms /terms terms_path

To create these pages, we are going to use a gem from the folks at thoughtbot called High Voltage. It is one of my favorite gems that allows you to quickly and easily create static pages plus it has an MIT License. As an aside, start checking the licenses of your gems before deciding to include them in your projects if you are not already doing so. Let’s get started.

Open the Gemfile file in the root of your project and add the following line:

gem 'high_voltage'

Next run bundle install to add the gem to your project:

bundle install

We are going to follow the directions found in the High Voltage README to create the following directory structure:

app/views/pages
            ├── about.html.erb
            ├── contact.html.erb
            ├── home.html.erb
            ├── privacy.html.erb
            └── terms.html.erb

We’ll do that by typing the following commands:

mkdir app/views/pages
touch app/views/pages/about.html.erb
touch app/views/pages/contact.html.erb
touch app/views/pages/home.html.erb
touch app/views/pages/privacy.html.erb
touch app/views/pages/terms.html.erb

Take a few minutes to edit each of those 5 files so they contain some content.

Edit the file about.html.erb in app/views/pages/ to contain:

<h1>About Us</h1>
<p>Find me in app/views/pages/about.html.erb</p>

Edit the file contact.html.erb in app/views/pages/ to contain:

<h1>Contact Us</h1>
<p>Find me in app/views/pages/contact.html.erb</p>

Edit the file home.html.erb in app/views/pages/ to contain:

<h1>Home Page</h1>
<p>Find me in app/views/pages/home.html.erb</p>

Edit the file privacy.html.erb in app/views/pages/ to contain:

<h1>Privacy Policy</h1>
<p>Find me in app/views/pages/privacy.html.erb</p>

Edit the file terms.html.erb in app/views/pages/ to contain:

<h1>Terms of Service</h1>
<p>Find me in app/views/pages/terms.html.erb</p>

Application Routing

Next, we’ll setup routing. As mentioned in the previous section, we are going to make home.html.erb the root url for our project. We need to include a redirect for any root url page otherwise High Voltage will serve the same content up twice, at http://example.com/ and http://example.com/home, and that can effect the page rank in search engines. In a previous section, we discussed the need to setup a number of routes, like about_path, contact_path, etc., we’ll do that now. After edits, below are the contents of my routes.rb file:

Bootstrap::Application.routes.draw do

  get '/about'    => 'high_voltage/pages#show', id: 'about'
  get '/contact'  => 'high_voltage/pages#show', id: 'contact'
  get '/privacy'  => 'high_voltage/pages#show', id: 'privacy'
  get '/terms'    => 'high_voltage/pages#show', id: 'terms'

  get '/home', to: redirect('/')

  root :to => 'high_voltage/pages#show', id: 'home'

end

You might have noticed a discrepancy at this point. By default, High Voltage will serve our static pages at http://www.example.com/pages/about. That isn’t what we want so we’ll need to tell the gem to serve up these pages from the root of the domain path. That is very easy to do. In the config/initializers/ directory create a new file called high_voltage.rb. It’s content should be:

# config/initializers/high_voltage.rb
HighVoltage.route_drawer = HighVoltage::RouteDrawers::Root

I just wanted to mentioned that, with the initializer change is made, High Voltage is smart enought to route requests for /about to the correct page even if someone were to forget to create a get '/about' => 'high_voltage/pages#show', id: 'about' entry in their routes.rb file. However without that line, we would not be able to use about_path because Rails would not know how to route our request.

At this point, let’s pause for a moment. Start up a development server using rails s and test the 5 pages. If you already have one running, please restart it. If you use WEBrick on the default port, those paths would be:

http://localhost:3000/
http://localhost:3000/about
http://localhost:3000/contact
http://localhost:3000/privacy
http://localhost:3000/terms
http://localhost:3000/home

Make sure that last URL automatically takes you to http://localhost:3000/. Worked for me so I’m hopeful I did not miss a step while typing this up.

A few helpers

Next, we need a way to customize an HTML title element so that it changes as users navigate from one page to the next. I’ll be using a modified version of Michael Hartl’s full_title helper that you will find in his excellent Ruby on Rails Tutorial. We are also going to setup a number of methods so that it is easier to customize the application later. These additional methods are site_name, site_url, meta_author, meta_description and meta_keywords.

Open your application_helper.rb file in app/helpers and let’s add some content.

module ApplicationHelper

  def site_name
    # Change the value below between the quotes.
    "Project Name"
  end

  def site_url
    if Rails.env.production?
      # Place your production URL in the quotes below
      "http://http://www.example.com/"
    else
      # Our dev & test URL
      "http://localhost:3000"
    end
  end

  def meta_author
    # Change the value below between the quotes.
    "Website Author"
  end

  def meta_description
    # Change the value below between the quotes.
    "Add your website description here"
  end

  def meta_keywords
    # Change the value below between the quotes.
    "Add your keywords here"
  end

  # Returns the full title on a per-page basis.
  # No need to change any of this we set page_title and site_name elsewhere.
  def full_title(page_title)
    if page_title.empty?
      site_name
    else
      "#{page_title} | #{site_name}"
    end
  end

end

Please make sure to change the site_name, site_url, meta_author, meta_description and meta_keywords to suit your needs. I found it useful to review this article about How to Use HTML Meta Tags. Also, if you are building a larger application, it might be useful to put all of these methods in a new file in app/helpers called layout_helper.rb it might improve the readability of your application.

There are a number of other ways to solve this problem. Some might advocate using metamagic, meta-tags, storing this data in yml files or any number of other solutions. I might tackle one in a future article so let me know about your favorite. Right now, let’s get the static layout finished.

Now, let’s go back to all the pages and add the following code on Line 1:

<% provide(:title, 'Page Name Here') %>

Be sure to change the text Page Name Here to the title you would like to see. If you would prefer that the home page of your project only display the site_name, you have two choices. First you can choose to not include the code above at all or, secondly which is my preference, you could use leave the value blank like I have below:

<% provide(:title, '') %>

Now that we’ve finished setting up these methods, let’s turn to the layout.

It’s layout time

We want to implement a layout similar to the Jumbotron Template for Bootstrap with a few changes minor changes. Navigate to your app/views/layouts directory and open the application.html.erb file. If you just want to grab the file rather than read what I’ve done, you can skip ahead to Final contents of application.html.erb

I’m only going to spend time talking about the Rails related tweaks we are making to the Bootstrap 3 layout.

The <Head>

We’ll start with the HTML head. Earlier in this article, we created a number of methods in the application_helper.rb file and now we are going to use them. Here’s the content of my HTML Head:

<head>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="<%= meta_description %>">
  <meta name="author" content="<%= meta_author %>">
  <meta name="Keywords" content="<%= meta_keywords %>">

  <title><%= full_title(yield(:title)) %></title>

  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>

  <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
  <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
  <![endif]-->
  <a href="#Top"></a>
</head>

You’ll see that we are populating the meta_description, meta_author and meta_keywords from the application_helper.rb file. That is done with these three lines:

<meta name="description" content="<%= meta_description %>">
<meta name="author" content="<%= meta_author %>">
<meta name="Keywords" content="<%= meta_keywords %>">

The title method we defined is also being used here:

<title><%= full_title(yield(:title)) %></title>

The Nav bar

Now we will turn our attention to the HTML body. At the top of our page, we wanted to have a navigation bar. On the left of this bar, we have our site_title that will respond to a click by taking you to our site_url. The most Rails like way to write this code is:

<%= link_to site_name, site_url, :class => "navbar-brand" %>

On the right side of our navigation bar, we want to show three buttons that direct you to their respective pages and have the list class of the button change if you are on its respective page.

<li class="<%= 'active' if current_page?(root_path) %>">
<li class="<%= 'active' if current_page?(about_path) %>">
<li class="<%= 'active' if current_page?(contact_path) %>">

If you find it redundant to have two links in the Nav bar pointing to the root url, just delete the entire home list item.

<li class="<%= 'active' if current_page?(root_path) %>"><%= link_to "Home", root_path %></li>

Then the only link to the root url with be the site_name on the left side of the Nav bar.

The Jumbotron

Support for the jumbotron necessitates code in the application.html.erb file because we need the jumbotron class to precend the container class. The latter is something I wanted in one place rather than having to remember to add it to each view later. So, we are going to check the current page and only display the jumbotron code when we are on the root url. That is done with the following:

<% if current_page?(root_path) %>
  <% if content_for?(:jumbotron) %>
    <div class="jumbotron">
      <div class="container">
        <%= yield :jumbotron %>
      </div>
    </div>
  <% end %>
<% else %>
  <div class="container">
<% end %>

We are also going to need to make modification the the home.html.erb file now to populate the jumbotron content. We’ll do that after we look at the application.html.erb footer.

The Footer

One of the things I wanted to include in this article was a Glyphicon. So in the bottom right corner of the footer, you will find the following code:

<%= link_to '<span class="glyphicon-class glyphicon glyphicon-chevron-up"></span> Back to top'.html_safe, '#Top' %>

Using html_safe is the prettiest way that I could think of to create the HTML required for Glyphicons. It produces the following HTML output:

<a href="#Top"><span class="glyphicon-class glyphicon glyphicon-chevron-up"></span> Back to top</a>

On the left side of the footer, we have a copyright line along with links to our Privacy and Terms pages.

&copy; <%= Time.now.year %> <%= site_name %>

<%= link_to "Privacy", privacy_path %></li>
<%= link_to "Terms", terms_path %></li>

If you would prefer to have range of years in your copyright line similar to:

© 2010 - 2012 Project Name

Then take a look at How to Automate Copyright Notice Updates in Ruby on Rails.

Revisiting home.html.erb

Let’s open the home.html.erb file in app/views/pages/. We need to add some content for our jumbotron. Before we do, just remember that if you would prefer not to have a jumbotron then just leave the value blank like this:

<% provide(:jumbotron, ''>

For thepurposes of this tutorial, I’m just going to add some default text. Please customize it to suit your needs.

<% provide(:jumbotron, '
    <h1>Hello, world!</h1>
    <p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
    <p><a class="btn btn-primary btn-lg" role="button">Learn more &raquo;</a></p>
'.html_safe)%>

The final contents of the home.html.erb file are at the end of this article.

Some improvements to the original CSS

If you followed my original article prior to a few updates, I used to include some overrides directly in the application.css file. Since then, I’ve made a few changes. My appication.css file, found in app/assets/stylesheets now contains:

/*
 *= require bootstrap_and_overrides
 *= require_self
 *= require_tree .
 */

In the same location app/assets/stylesheets, I have created a new file called bootstrap_and_overrides.css.scss and it contains:

@import "bootstrap.min";
@import "bootstrap-theme.min";

/* Move down content because we have a fixed navbar that is 50px tall */
body {
  padding-top: 50px;
  padding-bottom: 20px;
}

/* Override Rail's default error calls with Bootstrap 3 */
.field_with_errors {
  @extend .has-error;
}

/* Override Bootstrap 3 font location */
@font-face {
  font-family: 'Glyphicons Halflings';
  src: url('../assets/glyphicons-halflings-regular.eot');
  src: url('../assets/glyphicons-halflings-regular.eot?#iefix') format('embedded-opentype'), 
       url('../assets/glyphicons-halflings-regular.woff') format('woff'), 
       url('../assets/glyphicons-halflings-regular.ttf') format('truetype'), 
       url('../assets/glyphicons-halflings-regular.svg#glyphicons_halflingsregular') format('svg');
}

The .field_with_errors tweak came from Chris Oliver’s Trying Out Bootstrap 3.0.

Final contents of application.html.erb

<!DOCTYPE html>
<html>
<head>

  <meta charset="utf-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta name="description" content="<%= meta_description %>">
  <meta name="author" content="<%= meta_author %>">
  <meta name="Keywords" content="<%= meta_keywords %>">

  <title><%= full_title(yield(:title)) %></title>

  <%= stylesheet_link_tag    "application", media: "all", "data-turbolinks-track" => true %>
  <%= javascript_include_tag "application", "data-turbolinks-track" => true %>
  <%= csrf_meta_tags %>

  <!-- HTML5 shim and Respond.js IE8 support of HTML5 elements and media queries -->
  <!--[if lt IE 9]>
    <script src="https://oss.maxcdn.com/libs/html5shiv/3.7.0/html5shiv.js"></script>
    <script src="https://oss.maxcdn.com/libs/respond.js/1.3.0/respond.min.js"></script>
  <![endif]-->
  <a href="#Top"></a>
</head>
<body>
  <div class="navbar navbar-fixed-top navbar-inverse" role="navigation">
    <div class="container">
      <div class="navbar-header">
        <button type="button" class="navbar-toggle" data-toggle="collapse" data-target=".navbar-collapse">
          <span class="sr-only">Toggle navigation</span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
          <span class="icon-bar"></span>
        </button>
        <%= link_to site_name, site_url, :class => "navbar-brand" %>
      </div>
      <div class="collapse navbar-collapse">
        <ul class="nav navbar-nav pull-right">
          <li class="<%= 'active' if current_page?(root_path) %>"><%= link_to "Home", root_path %></li>
          <li class="<%= 'active' if current_page?(about_path) %>"><%= link_to "About", about_path %></li>
          <li class="<%= 'active' if current_page?(contact_path) %>"><%= link_to "Contact", contact_path %></li>
        </ul>
      </div><!-- /.nav-collapse -->
    </div><!-- /.container -->
  </div><!-- /.navbar -->

  <% if current_page?(root_path) %>
    <% if content_for?(:jumbotron) %>
      <div class="jumbotron">
        <div class="container">
          <%= yield :jumbotron %>
        </div>
      </div>
    <% end %>
  <% else %>
    <div class="container">
  <% end %>
  <%= yield %>
  <footer>
    <hr>
      <p class="pull-right">
        <%= link_to '<span class="glyphicon-class glyphicon glyphicon-chevron-up"></span> Back to top'.html_safe, '#Top' %>
      </p>
      <p>
        &copy; <%= Time.now.year %> <%= site_name %> 
        &bull;
        <%= link_to "Privacy", privacy_path %></li>
        &bull;
        <%= link_to "Terms", terms_path %></li>
      </p>
    </footer>
  </div> <!-- /container -->
</body>
</html>

Final contents of home.html.erb

<% provide(:title, '') %>

<% provide(:jumbotron, '
      <h1>Hello, world!</h1>
      <p>This is a template for a simple marketing or informational website. It includes a large callout called a jumbotron and three supporting pieces of content. Use it as a starting point to create something more unique.</p>
      <p><a class="btn btn-primary btn-lg" role="button">Learn more &raquo;</a></p>
  '.html_safe)%>

  <div class="container">
    <!-- Example row of columns -->
    <div class="row">
      <div class="col-md-4">
        <h2>Heading</h2>
        <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
        <p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
      </div>
      <div class="col-md-4">
        <h2>Heading</h2>
        <p>Donec id elit non mi porta gravida at eget metus. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus. Etiam porta sem malesuada magna mollis euismod. Donec sed odio dui. </p>
        <p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
     </div>
      <div class="col-md-4">
        <h2>Heading</h2>
        <p>Donec sed odio dui. Cras justo odio, dapibus ac facilisis in, egestas eget quam. Vestibulum id ligula porta felis euismod semper. Fusce dapibus, tellus ac cursus commodo, tortor mauris condimentum nibh, ut fermentum massa justo sit amet risus.</p>
        <p><a class="btn btn-default" href="#" role="button">View details &raquo;</a></p>
      </div>
    </div>

A sample application is now available on Github. Download it here.

If you have any suggestions or feedback about this article, please let me know in the comments, via email or on Twitter.

  • K.C. Barrett

    Hi, thanks for the article. Everything worked great, except I’m still having some difficulty with the glyphicons. I’ve copied your code exactly as is into my bootstrap_and_overrides.css.scss file, but the console gives a 404 error for the .woff, .ttf & .svg files. The actual error reads :”GET http://my_domain/fonts/glyphicons-halflings-regular.woff 404 (Not Found)”. Do you have any insight as to why this might be? Thanks again for the nice post.

    • I sent you an email with a few pointers but haven’t heard back. Did you follow the instructions in the first article? The link is in the first paragraph. If you did, are you running into problems in dev, test or production or all 3? Is your project on github where I can take a quick look?

      My email is in the last paragraph, feel free to contact me and I’ll see if I can’t help you out.

      — Richard

      • K.C. Barrett

        Thanks for the reply. I’ll check my spam folder to see if your email got lost in the shuffle.

  • trish

    Thanks for this very helpful. I tried styling the site_url which i thought was under navbar-brand class but seems only thing I can change is the font size. Can’t change color or anything. also if i remove the ‘inverse’ from nav heading, so it is not black it is almost impossible to customize it with other colors etc and move the main nav tabs to far right. Wondering if this is a feature of working with bootstrap.

    • You can definitely customize things. Are you making the changes in application.css or the bootstrap_and_overrides file?

  • oppih

    What if I made a folder in the pages/ folder, like pages/about/address.html.erb, pages/about/team.html.erb and so on. How to deal with the routes in such condition. And when I follow the high_voltage guide, I can use and it will lead me to the example.com/pages/about/address , how to make it something like example.com/about/address ? Because the ‘page_path’ keeps lead me to the old url, and I cannot figure out a new helper method. Thank you.

    • Check out the Top-level routes section on the High_Voltage gem page. Here’s the URL:

      https://github.com/thoughtbot/high_voltage#top-level-routes

      I’m assuming you are using the most current high_voltage gem. If you are using an older gem version, let me know.

      • oppih

        I solved this problem by adding a new route rule in the routes.rb : match ‘/*id’ => ‘high_voltage/pages#show’, :as => :static, :via => :get

  • oppih

    By the way, is it recommended to use `html_safe` in Rails 4? should I use `raw` method?

  • Fallon Blaser

    I love you for this, Richard, seriously. I’ve been learning Ruby/Rails/HTML/CSS through codeacademy and am finally trying to get my own website up and running. Your tutorial is unravelling some of the mystery behind incorporating a template into Rails 4, which I’ve been struggling with. I have three questions if you don’t mind answering them: 1) Using a bootstrap template with LESS, can I replace the “bootstrap_and_overrides.css.scss” with “bootstrap_and_overrides.css.less” after installing a less_rails gem? 2) The bootstrap template I’m using doesn’t call for a jumbotron, but uses a background-image url in the header. I can’t figure out how to get the image to show up – placing the image in the /public folder doesn’t work, nor in the /assets/images folder. I assume this has something to do with Rails? 3) Would replacing the standard glyphicons with Font Awesome’s icons be any different in instruction? Thank you for the tutorial!

    • You are very kind. Thanks for the positive feedback!

      These days if I were starting a new project with Bootstrap 3 and Font Awesome, I’d go Sass and use the two gems below. The usage instructions on both pages are clearly written and easy to implement.
      https://github.com/twbs/bootstrap-sass
      https://github.com/FortAwesome/font-awesome-sass

      FWIW, one reason not to use Less is that Bootstrap 4 is in alpha and they are moving from Less to Sass. See:
      http://blog.getbootstrap.com/2015/08/19/bootstrap-4-alpha/

      For the image, if you go with Sass, make sure you have the sass-rails gem installed, the image in the /assets/images folder and use something like:

      background-image: url(asset-path(‘image_name.png’))

      Please change the image_name.png to your actual image’s filename.

      If you have your heart set on Less, just reply and I’ll whip up a new project to try to answer your question(s).

      • Fallon Blaser

        Thank you so much, yes it does make sense to use Sass then. The image situation is still stumping me… I wonder if cloud9 is the problem. I’ve even started apps for brand new setups, yet the images put in the assets/images folder don’t get read. Take care Richard! Hope you’re still doing the Ted talks – one of my favorite podcasts is the Ted Radio Hour’s “How it All Began”

        • Sounds like you decided that Sass might be a good idea. Sorry to hear the image referenced in css still isn’t working. I put together a quickly application and just pushed it to github to help you out.

          https://github.com/rvangeilswyk/background-image-example

          I’ve added the podcast you mentioned to my player. Thanks for the suggestion!

          • Fallon Blaser

            Thank you thank you thank you!!! I’m so excited to be learning all of this!