Using Tailwind CSS with Phoenix

post image
Photo by @louis_moncouyoux on Unsplash

Published on: 22 December 2020

I wrote this little guide to help anyone try out Tailwind in their Elixir/Phoenix project. It works not just for local development, but also for production. I will be using Tailwind 2.0 and Phoenix 1.5.7. I wrote it for brand new projects, but you can skip the first section, and follow the remaining steps to add it to an existing project. Let’s get started.

Setting up a new project

Let’s scaffold a new Phoenix project, we’re going to call it tailwind:

$ mix phx.new tailwind --live

Notice that I’ve also included the --live argument, which will include Phoenix LiveView. You can skip this step, but if you haven’t tried LiveView, I’d definitely recommend you give it a shot.

You can answer with Y to install all dependencies, and then go into the project directory:

$ cd tailwind

Phoenix projects include a version of the Milligram CSS framework, which we don’t need. Let’s remove it:

$ rm assets/css/phoenix.css

Then edit assets/css/app.scss and remove this line:

@import "./phoenix.css";

Finally, rename assets/css/app.scss to app.css, since we’re no longer going to use Sass. We’ll replace it with PostCSS in just a moment.

$ mv assets/css/app.scss assets/css/app.css

Now we’re ready to add Tailwind.

Installing Tailwind and dependencies

The recommded way to use Tailwind is as a PostCSS plugin. The latest version of PostCSS at the time of writing is 8.0. The official guide also recommends adding autoprefixer, so we will include this, too. Finally, we will add postcss-loader so we can plug PostCSS into Webpack. This line will install everything in one go:

$ npm install --prefix assets --save-dev tailwindcss postcss postcss-loader autoprefixer

Next, we have to add some basic configuration files for both PostCSS and Tailwind. The following config for PostCSS is the bare minimum you need. Create a file assets/postcss.config.js and add the following:

module.exports = {
  plugins: {
    tailwindcss: {},
    autoprefixer: {},
  }
}

Next, create assets/tailwind.config.js and paste this:

module.exports = {
  purge: [
    "../**/*.html.eex",
    "../**/*.html.leex",
    "../**/views/**/*.ex",
    "../**/live/**/*.ex",
    "./js/**/*.js",
  ],
  darkMode: false, // or 'media' or 'class'
  theme: {
    extend: {},
  },
  variants: {
    extend: {},
  },
  plugins: [],
};

Note that we included a few items in the purge field. This is strongly recommended for production and will significantly reduce your final build size. You can add or remove paths here to make it work for your project setup. Other than this, this file is identical to what you’ll get from running npx tailwindcss init.

Configuring Webpack

Now that both Tailwind and PostCSS are configured, let’s add them to the build. Modify the module section of you webpack.config.js to replace it with this:

module: {
  rules: [
    {
      test: /\.js$/,
      exclude: /node_modules/,
      use: {
        loader: "babel-loader",
      },
    },
    {
      test: /\.[s]?css$/,
      use: [MiniCssExtractPlugin.loader, "css-loader", "postcss-loader"],
    },
  ],
}

I’ve also hard-coded NODE_ENV to production in the deploy script found in package.json. You can skip this if you’re already setting NODE_ENV in your build environment:

"scripts": {
  "deploy": "NODE_ENV=production webpack --mode production",
  "watch": "webpack --mode development --watch"
},

Our final step is to include the Tailwind styles in our project.

Including Tailwind

To make all Tailwind classes available, you have to import everything in app.css. We can use the special @tailwind macro here:

@tailwind base;
@tailwind components;
@tailwind utilities;

Styles are imported in app.js and extracted out using Webpack. We need one small change in app.js since we renamed the file from app.scss to app.css:

// We need to import the CSS so that webpack will load it.
// The MiniCssExtractPlugin is used to separate it out into
// its own CSS file.
import "../css/app.css"

Notice that we updated the extension of the import statement.

That’s it, we’re done. Now you can start using Tailwind’s CSS classes in your templates.

Running in production

Thanks to specifying which templates to purge and setting NODE_ENV variable, this setup should work in production, too. This is size of the app.css bundle when you run it in development:

Version: webpack 4.41.5
Time: 5276ms
Built at: 12/17/2020 21:13:49
                Asset       Size  Chunks             Chunk Names
       ../css/app.css   3.71 MiB     app  [emitted]  app
       ../favicon.ico   1.23 KiB          [emitted]
../images/phoenix.png   13.6 KiB          [emitted]
        ../robots.txt  202 bytes          [emitted]
               app.js    360 KiB     app  [emitted]  app

It contains all classes generated by Taiwind, which is whopping 3.71MB.

Now let’s run the deploy script:

Version: webpack 4.41.5
Time: 12084ms
Built at: 12/17/2020 21:16:34
                Asset       Size  Chunks             Chunk Names
       ../css/app.css   6.03 KiB       0  [emitted]  app
       ../favicon.ico   1.23 KiB          [emitted]
../images/phoenix.png   13.6 KiB          [emitted]
        ../robots.txt  202 bytes          [emitted]
               app.js    101 KiB       0  [emitted]  app
   app.js.LICENSE.txt   98 bytes          [emitted]

Since we haven’t started using any classes, and all unused CSS was removed, the production build ended being just 6.03KB. It is not zero KB because we get some CSS to reset styling across browsers.

This work is licensed under the Creative Commons BY-SA 4.0 International License