How to install TailwindCSS 2.0 on Ruby on Rails 6.1.1

In this tutorial, I’ll walk you step-by-step on how to install TailwindCSS 2.0 on Ruby on Rails.

TailwindCSS is “a utility-first CSS framework packed with classes that can be composed to build any design, directly in you markup.” – https://tailwindcss.com/

To follow along make sure you have the necessary tools/dependencies, and they are updated.

For this guide, I am using Ruby 3.0.0; Rails 6.1.1; Node.js 14.15.4 and yarn 1.22.10.

Generate a new rails application

Let’s start by creating a new rails application.

$ rails new rails-tailwind-demo

At the end you will see something like:

As you can see, starting with Rails 6, Webpacker is the default JavaScript compiler. So, all JavaScript code will be handled by Webpacker.

With the installation done, we can generate a root page so we can test the TailwindCSS installation.

$ bundle exec rails g controller pages home

Nothing fancy. We just generated a new controller (PagesController) with a single action “home“. We will edit our `config/routes.rb` file so that this will be our entry page.

Rails.application.routes.draw do
  root to: 'pages#home'
end

Now we can start our Rails server and check our page without any styling. We will also start the Webpack dev server, so that when we change a JavaScript file and reload the page, we don’t have to wait longer for the compilation process.

In one terminal window, run:

$ bundle exec rails s -b 0.0.0.0

And in other terminal window or tab, run:

$ bin/webpack-dev-server

If you visit http://localhost:3000 you will see something like the image below. Note that I slightly changed the text.

This is the page you will see without any style applied. We don’t even have taiwindcss configured yet.

Installing Tailwind CSS

TailwindCSS developers recommend that Tailwind is installed in most projects as a PostCSS plugin. Webpacker already comes with PostCSS as a dependency. So for now, we will only install tailwindcss and autoprefixer as JavaScript dependencies using yarn.

$ yarn add tailwindcss autoprefixer

Since Rails 6/Webpacker already includes PostCSS as a dependency, there is a PostCSS configuration file under the root of the project (postcss.config.js), where we need to add tailwindcss and autoprefixer as plugins:

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

Tailwind installation can be customized. For that a config file needs to be created. If you are using VS Code, there is a Tailwind extension that uses this configuration file to determine if the project uses TailwindCSS or not.

To create a Tailwind configuration file we need to run:

$ npx tailwindcss init

The previous command will create the Tailwind config file (/tailwind.config.js) at the project’s root folder.

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

Now, we need to add Tailwind to our CSS. For that, let’s start creating a new file (application.scss) under `app/javascript/packs/` with the following content:

// app/javascript/packs/application.scss
@import 'tailwindcss/base';
@import 'tailwindcss/components';
@import 'tailwindcss/utilities';

Also, open app/javascript/packs/application.js and import our new application.scss file like this:

import Rails from "@rails/ujs"
import Turbolinks from "turbolinks"
import * as ActiveStorage from "@rails/activestorage"
import "channels"

import "./application.scss"

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

With this change, we also need to update our main layout file`app/views/layouts/application.html.erb`. Note that we added a new stylesheet_pack_tag:

<head>
    <title>RailsTailwind</title>
    <meta name="viewport" content="width=device-width,initial-scale=1">
    <%= csrf_meta_tags %>
    <%= csp_meta_tag %>

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

With all this in place, we can now start or restart our webpack-dev-server and see if something changed.

PostCSS plugin tailwindcss requires PostCSS 8.

At this point if you try to start webpack-dev-server you will get an ERROR and the compilation will fail.

ERROR in ./app/javascript/packs/application.scss (./node_modules/css-loader/dist/cjs.js??ref–6-1!./node_modules/postcss-loader/src??ref–6-2!./node_modules/sass-loader/dist/cjs.js??ref–6-3!./app/javascript/packs/application.scss)
Module build failed (from ./node_modules/postcss-loader/src/index.js):
Error: PostCSS plugin tailwindcss requires PostCSS 8.
Migration guide for end-users:
https://github.com/postcss/postcss/wiki/PostCSS-8-for-end-users
at Processor.normalize (/Users/psantos/Projects/Learning/rails-tailwind/node_modules/postcss/lib/processor.js:153:15)
at new Processor (/Users/psantos/Projects/Learning/rails-tailwind/node_modules/postcss/lib/processor.js:56:25)
at postcss (/Users/psantos/Projects/Learning/rails-tailwind/node_modules/postcss/lib/postcss.js:55:10)
at /Users/psantos/Projects/Learning/rails-tailwind/node_modules/postcss-loader/src/index.js:140:12
ℹ 「wdm」: Failed to compile
.

We are getting this error because TailwindCSS 2.0 depends on PostCSS 8. But rigth now the installed Webpacker 5 depends on postcss-loader 3.0.0, which depends on postcss 7.0.0.

So, to get rid of this error, we need to force our application to use a postcss 8.x.x and postcss-loader 4.2.0. Note that, while I am writing this guide an updated version of postcss-loader is out. It is the version 5.0.0. I tried with this latest version, but it did not work.

To force our app to use PostCSS 8.x.x and postcss-loader 4.2.0, we need to add/install these dependencies explicitly by running the following command:

$ yarn add postcss [email protected]

Again, note that I am specifying the exact version for postcss-loader. Otherwise, it will install the latest version which is now 5.0.0 and will cause another error.

Now, we can try running the webpack-server-dev again.

Surprisingly when we try running the webpack-server-dev again, we got another error. Thankfully is a different error :), meaning we are progressing.

ERROR in ./app/javascript/packs/application.scss (./node_modules/css-loader/dist/cjs.js??ref–6-1!./node_modules/postcss-loader/dist/cjs.js??ref–6-2!./node_modules/sass-loader/dist/cjs.js??ref–6-3!./app/javascript/packs/application.scss)
Module build failed (from ./node_modules/postcss-loader/dist/cjs.js):
ValidationError: Invalid options object. PostCSS Loader has been initialized using an options object that does not match the API schema.options has an unknown property ‘config’. These properties are valid:
object { postcssOptions?, execute?, sourceMap?, implementation? }
at validate (/Users/psantos/Projects/Learning/modern-css-with-tailwind/rails-tailwind/node_modules/postcss-loader/node_modules/schema-utils/dist/validate.js:104:11)
at Object.loader (/Users/psantos/Projects/Learning/modern-css-with-tailwind/rails-tailwind/node_modules/postcss-loader/dist/index.js:43:29)
ℹ 「wdm」: Failed to compile.

Digging on the Internet, I found this issue: https://github.com/rails/webpacker/issues/2784

So, the temporary solution for this last error is to open config/webpack/environment.js and add the following content:

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

function hotfixPostcssLoaderConfig (subloader) {
  const subloaderName = subloader.loader
  if (subloaderName === 'postcss-loader') {
    if (subloader.options.postcssOptions) {
      console.log(
        '\x1b[31m%s\x1b[0m',
        'Remove postcssOptions workaround in config/webpack/environment.js'
      )
    } else {
      subloader.options.postcssOptions = subloader.options.config;
      delete subloader.options.config;
    }
  }
}

environment.loaders.keys().forEach(loaderName => {
  const loader = environment.loaders.get(loaderName);
  loader.use.forEach(hotfixPostcssLoaderConfig);
});

module.exports = environment

With these changes in place we can now start or restart our rails server and webpack-dev-server:

$ bundle exec rails s -b 0.0.0.0

and

$ bin/webpack-dev-server

Now, webpack-dev-server will be running and compile without error. And if we try to visit http://localhost:3000 we will see something different from what we saw in the beginning:

Tailwind is now reseting our css \o/

I will just update our app/views/pages/home.html.erb file, to add more example so we can see for sure that tailwind is working correctly. I will just copy and paste a code from https://tailwindcss.com/:

<div>
  <figure class="md:flex bg-gray-100 rounded-xl p-8 md:p-0">
  <img class="w-32 h-32 md:w-48 md:h-auto md:rounded-none rounded-full mx-auto" src="https://tailwindcss.com/_next/static/media/sarah-dayan.a8ff3f1095a58085a82e3bb6aab12eb2.jpg" alt="" width="384" height="512">
  <div class="pt-6 md:p-8 text-center md:text-left space-y-4">
    <blockquote>
      <p class="text-lg font-semibold">
        “Tailwind CSS is the only framework that I've seen scale
        on large teams. It’s easy to customize, adapts to any design,
        and the build size is tiny.”
      </p>
    </blockquote>
    <figcaption class="font-medium">
      <div class="text-blue-600">
        Sarah Dayan
      </div>
      <div class="text-gray-500">
        Staff Engineer, Algolia
      </div>
    </figcaption>
  </div>
  </figure>
</div>


Now, refreshing a page will give you something like:

Tailwind Card Example

That’s it. We have TailwindCSS 2.0 working with Rails 6.1

Complete Source code can be found here: https://github.com/psantos10/rails-tailwindcss

,,,,