We're working on something new! Hook Relay gives you Stripe-quality webhooks in minutes. Sign up for free today! Check out Hook Relay

Using Tailwind CSS with Rails

Tailwind CSS is a popular CSS framework that helps developers quickly build and style web pages with a unique utility-based approach. Unlike other CSS frameworks, it comes with its own build tooling. In this article, Jeffery Morhous walks us through setting up Tailwind CSS with Rails and Webpacker.

CSS is magical but time-consuming. Beautiful, functional, and accessible sites are a joy to use, but writing our own CSS is exhausting. Many CSS libraries, such as Bootstrap, have exploded in recent years, and Tailwind is leading the pack in 2021.

Although Rails does not come with Tailwind out of the box, this article will show you how to add Tailwind CSS to a new Ruby on Rails project, which will save you hours in design implementation. We'll also walk-through designing using Tailwind's utility classes! The first part of this tutorial will explain how to create a new Rails project and add Tailwind from the start. In the latter part, I'll show you how to add Tailwind to an existing project.

What is Tailwind CSS

Tailwind CSS empowers developers with utility classes. These predefined-defined CSS classes give you the building blocks to create any design directly from HTML. You can style any HTML element by simply appending one or more classes to it. Tailwind is a massive utility library that comes with all kinds of styles that you can combine to create custom designs faster than writing your own CSS.

For example, you could create a card view with a nice header by just applying some classes to the elements you want styled:

<div class="text-center max-w-sm rounded overflow-hidden shadow-lg">
  <h1 class="text-3xl font-black">This is a styled header inside a styled card element.</h1>
</div>

This is it! You don't have to go define these classes and painstakingly fight CSS to get it to look how you want. Tailwind has already defined enough of these utility classes to meet the most common styling needs.

It has quickly gained traction in the last year as a way for developers to save time while still creating consistently well-designed interfaces. Some developers love Tailwind for these reasons, and others hate it for the mess it creates in your HTML.

Setting up Tailwind with a New Rails App

For simplicity, we will cover building an app from scratch and creating a new Rails application. We'll be using the following versions for the examples:

  • Rails 6.1
  • Ruby 3.0.0

rbenv is a very standardized way to manage different Ruby versions. If you have homebrew, you can install it with brew install rbenv.

If you're using rbenv, you can install Ruby 3.0.0 with rbenv install 3.0.0.

Then, you can switch your current directory to Ruby 3.0.0 with rbenv local 3.0.0.

If it's a new version of Ruby, you'll want to use gem install rails.

Creating a New Rails Application

Now that you're set to go with Rails 6.1 and Ruby 3.0, you can create a new Rails application by running rails new tailwind-example. Replace tailwind-example with any other name for your project, but do note that you'll also have to swap it out in any other code/shell reference to the project name.

Next, change to the new project directory: cd tailwind-example.

Finally, serve your project locally to verify that everything is working correctly: rails server. Then, navigate over to localhost:3000 in your browser to see the Rails welcome page! If you see something like this, you're on the right path: A screenshot of the Rails welcome page

With a fresh Rails app up and running, you're ready to add Tailwind and start creating interfaces faster.

Installing Tailwind CSS

First, add TailwindCSS as a dependency by running yarn add tailwindcss.

Next, use the following command to create a tailwind configuration file in the proper place: npx tailwindcss init.

Now, open your project and your IDE of choice (hurray for VS Code!). Open the postcss.config.js file in your project's root directory and add require("tailwindcss"), to the list of imports. Here's what my postcss file looks like now:

module.exports = {
  plugins: [
    require('postcss-import'),
    require('postcss-flexbugs-fixes'),
    require("tailwindcss"),
    require('postcss-preset-env')({
      autoprefixer: {
        flexbox: 'no-2009'
      },
      stage: 3
    })
  ]
}

Next, create a file called application.css in app/javascript. Inside the CSS file, add the following imports:

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

Next, add import "../application.css"; to the list of imports in app/javascript/packs/application.js. This brings in the tailwind imports to webpacker. The file should look like this:

// This file is automatically compiled by Webpack, along with any other files
// present in this directory. You're encouraged to place your actual application logic in
// a relevant structure within app/javascript and only use these pack files to reference
// that code so that it will be compiled.

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"
import  "../application.css";

Rails.start()
Turbolinks.start()
ActiveStorage.start()

To use Tailwind across your application, you have to import this webpack reference. In app/view/layouts/application.html.erb

Tailwind requires PostCSS 88, and Rails 6 has not updated yet. Thanks to their documentation, we can easily fix this issue by installing Tailwind's compatibility build. Just run the following to fix it:

npm uninstall tailwindcss postcss autoprefixer
npm install -D tailwindcss@npm:@tailwindcss/postcss7-compat @tailwindcss/postcss7-compat postcss@^7 autoprefixer@^9

Testing It Out

To show off our new CSS library, we'll create a new view, controller, and model. This is easy with Rails scaffolding: rails generate scaffold User email:string password:string.

Next, run the database migration to create the User table: rake db:migrate.

We now have a user model and basic CRUD capabilities with both views and controller actions. Let's go ahead and set the users' index page as the root of our application. Do this by adding root 'users#index' to config/routes.rb. Your routes file should now look like this:

Rails.application.routes.draw do
  root 'users#index'
  resources :users
  # For details on the DSL available within this file, see https://guides.rubyonrails.org/routing.html
end

This directs any traffic at the root of the application to our users' index page. Restart your Rails server with rails server, and when you go to localhost:3000 now, you should see something like this: A screenshot of the users' index page

This isn't very interesting for two reasons:

  1. There are no users.
  2. It's ugly.

It's easy for us to fix the first problem by using the application itself. Tailwind will help us out with the second on. Go ahead and click the "New User" link on the index page and create a few users with fake passwords.

Now, to confirm that we have Tailwind CSS set up properly with our project, let's add the following HTML to the very top of app/views/users/index.html.erb:

<div class="max-w-lg mx-auto mt-16 text-center max-w-sm rounded overflow-hidden shadow-lg p-10">
  <h1 class="mb-4 text-3xl font-black">Here are all our users!</h1>
  <p class="text-lg leading-snug">If this looks nice, it means Tailwind is set up properly.</p>
</div>

This is just a div with a header and a paragraph inside. Each is styled with a bit of Tailwind CSS. As you can tell, all we had to do to style it was add appropriate classes to each element. If it's configured properly, your user page should look like this now: A screenshot of the users' index page styled with Tailwind

Reducing Asset Size with PurgeCSS

Tailwind CSS assets are actually massive. As you can probably imagine, the definition of all those utility classes adds up. It makes sense, then, to only include definitions that you are actually using. Fortunately, Tailwind CSS comes with a built-in purge feature that does just that! You don't even have to manually pick and choose what stays and what goes.

All you have to do is tell Tailwind's configuration where to look, and it will automatically go through and remove the definitions of any unused CSS classes. This lets us optimize our application for file size and performance while still retaining the option to use more utility classes at any point.

Open the tailwind.config.js file in the project's root directory. It should look like this now:

module.exports = {
  purge: [],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

Conveniently, all we have to do is give the file path to anywhere we might have used Tailwind CSS classes. For this example, we only used Tailwind in our .html.erb files, so that's all we'll provide the path for. However, if your application uses a heavier weight front-end, such as React or Vue, you'll want to provide these paths too.

After we add our paths to the config file, it should look like this:

module.exports = {
  purge: [
    "./app/**/*.html.erb",
  ],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
}

With this configuration, unused CSS classes will be excluded when you compile with your NODE_ENV set to production.

Using Tailwind CSS Classes to Save Time

The whole point of Tailwind CSS is to help you design better, more consistently, and faster. Styling your page is as easy as adding the right class names. Glancing through the documentation is super helpful to get yourself oriented, but let's walk through building out our user views to give you some idea how easy Tailwind makes styling.

Styling the User Index Page

The first thing we'll do for our index page is put all the table stuff in a div, so we can apply some styles as a whole. We'll give that div a class of class="p-10" which is 10px of padding. We'll also apply some styling to our h1 tag for users. The 3 classes you'll see below are mostly self explanatory, but it's worth noting that mb-4 means adding 4px of margin on the bottom. After these changes, the User Index page should read like this:

<div class="max-w-lg mx-auto mt-16 text-center max-w-sm rounded overflow-hidden shadow-lg p-10">
  <h1 class="mb-4 text-3xl font-black">Here are all our users!</h1>
  <p class="text-lg leading-snug">If this looks nice, it means Tailwind is set up properly.</p>
</div>

<div class="p-10">
  <h1 class="mb-4 text-4xl font-black">Users</h1>
  <table>
    <thead>
      <tr>
        <th>Email</th>
        <th>Password</th>
        <th colspan="3">Actions</th>
      </tr>
    </thead>

    <tbody>
      <% @users.each do |user| %>
        <tr>
          <td><%= user.email %></td>
          <td><%= user.password %></td>
          <td><%= link_to 'Show', user %></td>
          <td><%= link_to 'Edit', edit_user_path(user) %></td>
          <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
  <br>
  <%= link_to 'New User', new_user_path %>
<div>

This is pretty basic, so let's spice it up a bit more. I added some classes for the table and the button, and my resulting code is this:

<div class="max-w-lg mx-auto mt-16 text-center max-w-sm rounded overflow-hidden shadow-lg p-10">
  <h1 class="mb-4 text-3xl font-black">Here are all our users!</h1>
  <p class="text-lg leading-snug">If this looks nice, it means Tailwind is set up properly.</p>
</div>

<div class="p-10">
  <h1 class="mb-4 text-4xl font-black">Users</h1>
  <table class="min-w-full table-auto">
    <thead class="bg-gray-800 text-gray-300" >
      <tr>
        <th>Email</th>
        <th>Password</th>
        <th colspan="3">Actions</th>
      </tr>
    </thead>

    <tbody>
      <% @users.each do |user| %>
        <tr class="border-4 border-gray-200">
          <td><%= user.email %></td>
          <td><%= user.password %></td>
          <td><%= link_to 'Show', user %></td>
          <td><%= link_to 'Edit', edit_user_path(user) %></td>
          <td><%= link_to 'Destroy', user, method: :delete, data: { confirm: 'Are you sure?' } %></td>
        </tr>
      <% end %>
    </tbody>
  </table>
  <br>
  <%= link_to 'New User', new_user_path, class: "bg-indigo-500 text-white px-4 py-2 border rounded-md hover:bg-white hover:border-indigo-500 hover:text-black" %>
<div>

Adding these changes results in our user index page looking like this: A screenshot of the users' index page styled with even more Tailwind

Styling the New User Page

While we're at it, let's restyle the existing "New User" page. Currently, it looks like this: A screenshot of the new user page without styling

First, we'll apply our desired classes in the new view for users. Here's what mine looks like:

<div class="bg-grey-lighter min-h-screen flex flex-col">
  <div class="container max-w-sm mx-auto flex-1 flex flex-col items-center justify-center px-2">
    <div class="bg-white px-6 py-8 rounded shadow-lg text-black w-full">
        <h1 class="mb-8 text-3xl text-center">Sign up</h1>
        <%= render 'form', user: @user %>

        <div class="text-center text-sm text-grey-dark mt-4">
            By signing up, you agree to the
            <a class="no-underline border-b border-grey-dark text-grey-dark" href="#">
                Terms of Service
            </a> and
            <a class="no-underline border-b border-grey-dark text-grey-dark" href="#">
                Privacy Policy
            </a>
        </div>
    </div>

    <div class="text-grey-dark mt-6">
      Already have an account?
      <%= link_to 'Sign In', '#', class: "no-underline border-b border-blue text-blue"%>
    </div>
  </div>
</div>

Now, that's pretty good except the form itself isn't styled yet! In app/views/users/_form.html.erb, we still need to make some edits. Here's what mine looks like:

<%= form_with(model: user) do |form| %>
  <% if user.errors.any? %>
    <div id="error_explanation">
      <h2><%= pluralize(user.errors.count, "error") %> prohibited this user from being saved:</h2>

      <ul>
        <% user.errors.each do |error| %>
          <li><%= error.full_message %></li>
        <% end %>
      </ul>
    </div>
  <% end %>

  <div class="field">
    <%= form.label :email %>
    <%= form.text_field :email, class: "block border border-grey-light w-full p-3 rounded mb-4"  %>
  </div>

  <div class="field">
    <%= form.label :password %>
    <%= form.text_field :password, class: "block border border-grey-light w-full p-3 rounded mb-4" %>
  </div>

    <%= form.submit "Sign Up", class: "w-full text-center py-3 rounded bg-indigo-600 text-white hover:bg-green-dark focus:outline-none" %>
<% end %>

This is a lot of edits and some classes we haven't talked about, but everything you'd need is covered in the documentation. After applying these classes, our New User page now looks like this! A screenshot of the new user page WITH styling

Adding Tailwind to an Existing App

Odds are good that if you want to use Tailwind, you're not starting from a blank slate. You probably have an existing project with lots of custom CSS and even other frameworks, such as bootstrap. That's totally fine!

Assuming that your existing application is running with Webpacker, you can follow the steps outlined in "Installing Tailwind CSS" without changes! If you don't have webpacker installed, add it to the gemfile, run bundle install and then bundle exec rails webpacker:install, and then follow the Tailwind installation steps.

You can then mix and match with your existing CSS and Tailwind CSS!

Happy styling!

Honeybadger has your back when it counts. We're the only error tracker that combines exception monitoring, uptime monitoring, and cron monitoring into a single, simple to use platform.

Our mission: to tame production and make you a better, more productive developer. Learn more

author photo

Jeffrey Morhous

Jeff is a Computer Science and Engineering Student at The Ohio State University and freelance Software Engineer. He loves creating new things that make life more interesting, and learning as much he can on the way. In his spare time, he loves to play guitar, hike, and work on cars.


“We’ve looked at a lot of error management systems. Honeybadger is head and shoulders above the rest and somehow gets better with every new release.”
Michael Smith
Try Error Monitoring Free for 15 Days
Are you using Bugsnag, Rollbar, or Airbrake for your monitoring? Honeybadger includes exception, uptime, and check-in monitoring — all for probably less than you’re paying now. Discover why so many companies are switching to Honeybadger here.
Try Error Monitoring Free for 15 Days
Stop digging through chat logs to find the bug-fix someone mentioned last month. Honeybadger's built-in issue tracker keeps discussion central to each error, so that if it pops up again you'll be able to pick up right where you left off.
Try Error Monitoring Free for 15 Days
"Wow — Customers are blown away that I email them so quickly after an error."
Chris Patton
Try Error Monitoring Free for 15 Days