Adding Basic Tailwind CSS Support to Rails6

One common task when starting a new Rails application is to integrate with a CSS library or design framework. Because the CSS landscape has enjoyed quite a bit of expansion over the past few years, one of the more recent popular design frameworks is Tailwind CSS.

With that in mind, the focus of this tutorial is to go through the process of adding tailwindcss to your Rails 6 project.

Let’s dive in!

Assumptions & Goals

  • You are on a system that has Ruby on Rails 6 setup and available to you
  • We want to have TailwindCSS available for use with our front end

Getting Started

Open your shell and head to your favorite project directory. Then let’s rails new the project!


rails new rails-use-tailwindcss

Once Rails and the bundler finish setting up the new project, let’s get it committed to the git repository that Rails initializes for you.


cd rails-use-tailwindcss
git add .
git commit -m "initial commit"

NB: I may forget to write / mention it from time to time, but it’s *usually* a good idea to “sanity check” your system configuration by running bin/rails s before you do anything else, just to quickly verify your app loads ok.

add Tailwind CSS via Yarn

To help deal with CSS and JavaScript for your Rails application, a default Rails 6 project comes with 2 front end technologies: Yarn and Webpack:

Yarn is a package manager, which is a tool similar to Bundler for Rails, only we strictly use it for JavaScript packages. While Webpack is a utility that takes all of our JavaScript, CSS, and creates an optimized JavaScript “blob” for us to use. Try not to worry if that wording didn’t make too much sense, as you start using them you begin to see where they fit into the overall picture.

Back on the command line, use Yarn to install Tailwind CSS.


yarn add tailwindcss

generate a Tailwind configuration file

Once yarn finishes installing everything, you need to create a new folder and file:


mkdir app/javascript/css
touch app/javascript/css/application.css

To the newly created application.css, you need to add some basic tailwindcss imports:


@import "tailwindcss/base";
@import "tailwindcss/components";
@import "tailwindcss/utilities";

now make use of tailwind itself to generate a configuration file,


yarn tailwind init app/javascript/css/tailwind.js

updating the main application layout

With those updates to the CSS, it’s time to add a new stylesheet_pack_tag to the main application layout. Open app/views/layouts/application.html.erb, and add a new entry under the original two:


<%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track': 'reload' %>
<%= javascript_pack_tag 'application', 'data-turbolinks-track': 'reload' %>
<%= stylesheet_pack_tag 'application', 'data-turbolinks-track': 'reload' %>

updating the main javascript application pack

In order to ensure the new CSS files we’ve created make it into the application javascript pack, we need to add a reference to it.

Open up app/javascript/packs/application.js and add a new require at the end of the file:


require("../css/application.css")

updating the postcss.config.js

We’re almost done. Tailwind makes use of Postcss, so we need to make some modifications to the postcss.config.js in your application root. Open it up and update it to look like the following:


module.exports = {
    plugins: [
        require('postcss-import'),
        require('postcss-flexbugs-fixes'),
        require('tailwindcss')('./app/javascript/css/tailwind.js'),
        require('autoprefixer'),
        require('postcss-preset-env')({
            autoprefixer: {
                flexbox: 'no-2009'
            },
            stage: 3
        })
    ]
};

generate a static home page

Let’s generate a static home page to verify our updated CSS setup.


rails g controller pages home

Update the root Route in config/routes.rb to use the new static page:


Rails.application.routes.draw do
  get '/home' => 'pages#home'
  root 'pages#home'
end

Finally, let’s put some basic Tailwind in the new app/views/pages/home.html.erb view.


<nav class="bg-white px-8 pt-2 shadow-md">
  <div class="-mb-px flex justify-center">
    <a class="no-underline text-teal-dark border-b-2 border-teal-dark uppercase tracking-wide font-bold text-xs py-3 mr-8" href="#">
      Home
    </a>
    <a class="no-underline text-grey-dark border-b-2 border-transparent uppercase tracking-wide font-bold text-xs py-3 mr-8" href="#">
      About
    </a>
    <a class="no-underline text-grey-dark border-b-2 border-transparent uppercase tracking-wide font-bold text-xs py-3 mr-8" href="#">
      Pricing
    </a>
  </div>
</nav>

You can find the code for this project on my github - https://github.com/erikyuzwa/rails-use-bootstrap

If you found this tutorial helpful, please share it!

Adding Bootstrap4 to Rails6

One common task when starting a new Rails application is to integrate with a CSS library or design framework. While the CSS landscape has enjoyed quite a bit of expansion over the past few years, one of the most popular design frameworks is still Bootstrap 4.

While a LOT of that popularity is due to the success of Bootstrap 3, I think it does a lot more than just ride on its predecessors coat tails. It’s definitely an attempt on its own, of a modern design experience.

With that in mind, the focus of this tutorial is to go through the process of adding Bootstrap4 to your Rails 6 project.

Let’s jump into it!

Assumptions & Goals

  • You are on a system that has Ruby on Rails 6 setup and available to you
  • We want to have Bootstrap4 available to our front end

Getting Started

Open your shell and head to your favorite project directory. Then let’s rails new the project!


rails new rails-use-bootstrap

Once Rails and the bundler finish setting up the new project, let’s get it committed to the git repository that Rails initializes for you.


cd rails-use-bootstrap
git add .
git commit -m "initial commit"

NB: I may forget to write / mention it from time to time, but it’s *usually* a good idea to “sanity check” your system configuration by running bin/rails s before you do anything else, just to quickly verify your app loads ok.

add Bootstrap 4 and dependencies via Yarn

To help deal with CSS and JavaScript for your Rails application, a default Rails 6 project comes with 2 front end technologies: Yarn and Webpack:

Yarn is a package manager, which is a tool similar to Bundler for Rails, only we strictly use it for JavaScript packages. While Webpack is a utility that takes all of our JavaScript, CSS, and creates an optimized JavaScript “blob” for us to use. Try not to worry if that wording didn’t make too much sense, as you start using them you begin to see where they fit into the overall picture.

Back on the command line, use Yarn to install Bootstrap 4 and its two dependencies, jQuery and popper.js.


yarn add bootstrap jquery popper.js

Once yarn finishes installing everything, let’s go into the webpack configuration. Open up config/webpack/environment.js. It starts as a fairly empty file with only a couple of lines, but we need to expand it a bit.


const {environment} = require('@rails/webpacker')
const webpack = require('webpack')

environment.plugins.append('Provide', new webpack.ProvidePlugin({
    $: 'jquery',
    jQuery: 'jquery',
    Popper: ['popper.js', 'default']
}))

module.exports = environment

This is some handy configuration into Webpack, so that JQuery and Bootstrap references can be made from your JavaScript modules without needing to always include them in each one.

Import Bootstrap into your application-wide JavaScript. Open up app/javascript/packs/application.js and add the following line to the very bottom of the file:


    require('bootstrap')

add Bootstrap 4 to Webpack

In Rails 6, it’s pretty clear that more of the front end asset management is heading towards the use of Webpack.

Let’s add a new SASS stylesheet to handle our Bootstrap import. Create a new folder / file at app/javascript/stylesheets/styles.scss:


@import 'bootstrap/dist/css/bootstrap';

Now let’s go back into app/javascript/packs/application.js and add a reference to the new stylesheet:


import '../stylesheets/styles`
require('bootstrap')

generate a static home page

Let’s generate a static home page to verify our updated CSS setup.


rails g controller pages home

Update the root Route in config/routes.rb to use the new static page:


Rails.application.routes.draw do
  get '/home' => 'pages#home'
  root 'pages#home'
end

Finally, let’s put some basic Bootstrap in the new app/views/pages/home.html.erb view.


<nav class="navbar navbar-expand-sm bg-light">
  <ul class="nav justify-content-center">
    <li class="nav-item">
      <a class="nav-link" href="#">Home</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="#">About</a>
    </li>
    <li class="nav-item">
      <a class="nav-link" href="#">Pricing</a>
    </li>
  </ul>
</nav>
<div class="container">
    <h1>Pages#home</h1>
    <p>Find me in app/views/pages/home.html.erb</p>
</div>

You can find the code for this project on my github - https://github.com/erikyuzwa/rails-use-bootstrap

If you found this tutorial helpful, please share it!

Building Static Content Pages in Rails6

One of the most common starting points of a website or web application are pages that provide mostly unchanging content. These are often referred to as “static” pages.

There are usually three reasons for this; one, most web applications or sites are trying to build a connection with the visitor, and I’m not talking about the technical network connection. From the second you arrive at a website, the company or people behind it are trying to create a human connection with you. Secondly, most web applications / sites rely on communication with a database. While this communication is speedier than ever, Google has shown statistics in the past that the bounce rate (eg. visitors leaving your site) is proportional to the time it takes to load the starting home page. In other words, the longer it takes to load your website, the faster people are leaving it. The third reason, is that static pages are often used as “landing pages” - content that highlights some product or service that you want to attract attention to.

With that in mind, the focus of this tutorial is to go through the process of putting some static pages together in Rails 6.

I’m super excited to help you make this happen!

Assumptions & Goals

  • You are on a system that has Ruby on Rails 6 setup and available to you
  • We want to have our application start on a “default” Home page

Getting Started

Open your shell and head to your favorite project directory. Then let’s rails new the project!

rails new rails-static-pages

Once Rails and the bundler finish setting up the new project, let’s get it committed to the git repository that Rails initializes for you.

cd rails-static-pages
git add .
git commit -m "initial commit"

NB: I may forget to write / mention it from time to time, but it’s *usually* a good idea to “sanity check” your system configuration by running bin/rails s before you do anything else, just to quickly verify your app loads ok.

Now let’s get a welcome page going. Usually, you’re explaining your product, or your personal / company goals. Let’s use the handy rails generate to help us out.

NB: remember you can use rails g as a shortcut for rails generate

rails g controller pages

Once the generation is finished, find app/controllers/pages_controller.rb. By default, it’s an empty controller, so let’s add a new action to handle our static page, called home.

class PagesController < ApplicationController
    def home
    end
end

With an action in our controller defined, let’s create a view to match. You’ll notice that an app/views/pages folder was created during the generation process. To match the view with our controller action, create a file app/views/pages/home.html.erb.

You can put anything in there…right now we just need to verify that the view we want is what we see.

<h1>Home Page!</h1>

creating the route

With the controller and view setup, let’s update Rails so that it can find it. Open up the config/routes.rb:


Rails.application.routes.draw do
    get "/home" => "pages#home
end

You’re adding a url mapping here. This new mapping means that whenever your browser uses /home, then Rails can find the controller / view template you are trying to get to.

Let’s test it out!

start the server

Since we have a controller / view template configured, let’s see how it looks right now in the browser. From the root of your project folder:


bin/rails s

Open your browser to http://localhost:3000/home. You should be able to confirm the words Home Page!!

adding an rspec test for this controller

With our proper home page displaying correctly, let’s add a unit test for it! By default, Rails sets us up with RSpec for handling tests.

Navigate to test/controllers/pages_controller_test.rb. To help demonstrate, let’s create a unit test for the home static page, but let’s start off making it “fail”.

The test itself should be empty (or have some default comments inserted by Rails), so let’s create:


class PagesControllerTest < ActionDispatch::IntegrationTest
    test "should get the home page" do
        get pages_home_url
        assert_response :success
    end
end

Now run bin/rails test.

NB: If you get a screen full of strange errors, it’s likely due to the fact that we haven’t created our database yet. Go ahead and run bin/rails db:migrate

If all goes well, you should see some output like:


## Running:

.

1 runs, 1 assertions, 0 failures, 0 errors, 0 skips

There’s some other timing information in there which will perhaps be slightly different from machine to machine, but the important output is seeing that 1 assertions display - eg. test passed!

now we break the unit test

Just to help underscore how helpful test coverage is as your application grows, let’s change our url in the config/routes.rb file without updating the unit test.

Change:

  get "/home" => "pages#home"

To:

  get "/marketing" => "pages#home"

Now run the unit tests again with bin/rails test… what happens?

We get an error!

Imagine this app grows over time, and has over 50 URL mappings. Not all of them would be to static pages, but just 50+ route mappings in general. How will you be able to keep track of which ones are still active, and which ones are not used at all anymore?

adding a root Route

With our static pages configured, there’s the question of what Rails refers to as the root Route. This is usually the default index page of your application or website that is displayed when you visit the website - in our specific case, this would be the “default” page that we want shown when we hit http://localhost:3000.

Let’s go back into config/routes.rb and add an entry for root:


Rails.application.routes.draw do
    get "/home" => "pages#home

    # define our root / default route
    root "pages#home"
end

In other words, we’re creating another url mapping for the default / root Route.

Now open your browser, and head to http://localhost:3000. Instead of the familiar “Yay! You’re on Rails!” page, Rails will take you to pages#home.

Let’s update our test coverage!

Go back into test/controllers/pages_controller_test.rb, and add a new unit test for root_url:


test "root route should get the home page" do
    get root_url
    assert_response :success
end

Are we done? Not quite.

Where’s the flaw in our testing strategy? Hint: content

Our unit tests are able to determine if the url in question resolves, but not that we’ve got the correct page contents.

How do we do this?

One way is to make use of some special ruby functions, provide and yield. But to REALLY help illustrate how handy they are, we’re going to quickly add another static page, About.

adding an About static page

This is straight repetition of what we already did for the Home static page, so this should take no time at all.

For a good challenge, stop reading right here and try adding it for yourself (don’t forget the new unit test!).

Back? Okay, let’s do a recap.

app/controllers/pages_controller.rb:


class PagesController < ApplicationController
    # Action to handle the About view
    def about
    end

    # Action to handle the Home view
    def home
    end
end

That’s the PagesController.

Let’s add a new page for the HTML template, app/views/pages/about.html.erb:


<h1>About</h1>

Next, we update the config/routes.rb file:


Rails.application.routes.draw do
    get "/about" => "pages#about"
    get "/home" => "pages#home"

    # define our root / default route
    root "pages#home"
end

Finally, update the PagesControllerTest in test/controllers/pages_controller_test.rb:


  test "should get the about page" do
    get about_url
    assert_response :success
  end

Phew. Not too bad at all, considering we added a brand new route!

setting up provide and yield (and title)

Our strategy is to update the actual document <title></title> element within each view. This will help us greatly when updating our tests to confirm the page we want is the page we get.

First, let’s update our main application layout file, app/views/layouts/application.html.erb:


<% provide(:title, "") %>
<!DOCTYPE html>
<html>
    <head>
        <title><%= yield(:title) %></title>
        *snip*

The key updates here is to add the provide call at the top of the page. Next, we’re going to update the <title> section to call yield(:title).

Next, go back to your Home view template at app/views/pages/home.html.erb:


<% provide(title, "Home") %>
<h1>Home</h1>

Next, return to the About view template at app/views/pages/about.html.erb:


<% provide(title, "About") %>
<h1>About</h1>

Finally, let’s go back to our unit tests and update them to test the <title> elements within each request:


class PagesControllerTest < ActionDispatch::IntegrationTest

    test "should get the about page" do
        get about_url
        assert_response :success
        assert_select "title", "About"
    end

    test "should get the home page" do
        get home_url
        assert_response :success
        assert_select "title", "Home"
    end

    test "root route should get the home page" do
        get root_url
        assert_response :success
        assert_select "title", "Home"
    end
end

We went through quite a bit of steps here. To recap, we updated the main application layout file, to yield() any given title string within each view. Then we went into our 2 views, and just made sure to specific a different title for each one - Home and About.

Lastly, we updated the unit tests to call assert_select to query our title element within each resulting page to make sure that the title element we’re expecting, is the one we’re getting.

Go ahead and run bin/rails test to verify everything.

You can find the code for this project on my github - https://github.com/erikyuzwa/rails-static-pages

If you found this tutorial helpful, please share it!