Get started with Tailwind in Phoenix

This post was updated 04 May - 2022

phoenix tailwind esbuild

This tutorial is updated for Tailwind 3 and Phoenix 1.6.7

A new Phoenix app is generated with a minimal css framework and some default styles. However, my favourite CSS framework is Tailwind. Tailwind CSS is also what the Phoenix community seems to prefer at the moment and it is the "T" in the Petal stack. So in this totorial I will go through the steps of setting in up.

## Step 1 - Install Required Packages

There are two options to install Tailwind. By using javascript or the preferred option go with the Hex package. The Hex package is a wrapper around the Tailwind CLI and that is the simplest and fastest way to get up and running with Tailwind CSS from scratch.

So, in mix.exs add:

# mix.exs
def deps do
    # other packages
    {:tailwind, "~> 0.1", runtime: Mix.env() == :dev}

Then run mix deps.get

When you want to deploy your app, also want to run the tailwind commant to prepare the css bundle so add it before esbuild command.

# mix.exs
defp aliases do
    # other aliases
    "assets.deploy": ["tailwind default --minify", "esbuild default --minify", "phx.digest"]

Next step is to add the package configuration in the config.exs

# config/config.exs
config :tailwind,
  version: "3.0.24",
  default: [
    args: ~w(
    cd: Path.expand("../assets", __DIR__)

NOTE the "3.0.24" above corresponds to the version of Tailwind. Just lookup the latest version of Tailwind CSS on the tailwind website and use that.

In the config above, it points to a tailwind.config.js. Lets create a basic one:

// assets/tailwind.config.js
module.exports = {
  mode: 'jit',
  content: [
  plugins: [

NOTE this is now setup to run the built in purge to remove unused styles everytime you update either a js file or a file in the application web folder. If you have template files somewhere else, you can add them to the list.

Last configuration option to do is to setup the file watch. In config/dev.exs there are some watchers options and below the esbuild, add:

watchers: [
  esbuild: {Esbuild, :install_and_run, [:default, ~w(--sourcemap=inline --watch)]},
  tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]} # Add this

So, in theory, I could start the server now but all should look the same. That is because I haven't setup Tailwind in my CSS yet.

Step 2 - Add Tailwind to the CSS

When packages are installed and configured, the actual fun part start. Replace existing content in assets/css/app.css and add:

NOTE I can remove the import to @import "./phoenix.css"; and delete the phoenix.css file.

At this point, there is a good idea to start or restart the server to see if everything works as expected. If it does, we should see something like:

We know that it works because the font is the default Tailwind font on OS X.

STEP 3 - Update the layouts

What is left to do is to test this with updating the layout for the application. And I want to have a fixed navbar in the top and a sticky footer in the bottom.

So in the file lib/tutorial_web/templates/layout/root.html.heex

Change the layout to:

<html lang="en" class="h-full">
  <body class="pt-24 flex flex-col h-full">
      <nav class="fixed top-0 left-0 right-0 z-30 bg-white shadow">
        <div class="container mx-auto px-6 py-3 md:flex md:justify-between md:items-center">
          <div class="flex justify-between items-center">
              <a class="inline-block" href="/">
                <img src={Routes.static_path(@conn, "/images/phoenix.png")} class="h-8 object-contain" />
          </div>          <div class="flex items-center">
            <div class="flex flex-col md:flex-row md:mx-6">
              <a href="https://hexdocs.pm/phoenix/overview.html" class="my-1 text-sm text-gray-700 font-medium hover:text-indigo-500 md:mx-4 md:my-0">Get Started</a>
              <%= if function<i>exported?(Routes, :live</i>dashboard_path, 2) do %>
                <%= link "LiveDashboard", to: Routes.live<i>dashboard</i>path(@conn, :home), class: "my-1 text-sm text-gray-700 font-medium hover:text-indigo-500 md:mx-4 md:my-0" %>
              <% end %>
    </header>    <%= @inner_content %>    <footer class="bg-gray-700 mt-auto py-3">
      <div class="container pb-4 max-w-4xl mx-auto text-center text-gray-300 text-sm">
        &copy; <%= DateTime.utc_now.year %> Phoenix Tutorials

Then, since I have installed Phoenix with LiveView, I also need to update the (inner) templates:

// lib/tutorial_web/templates/layout/app.html.heex
// lib/tutorial_web/templates/layout/live.html.heex<main role="main" class="container mx-auto max-w-3xl px-4">


After more than 3 years with Tailwind, I am still happy. The productivity boost is of the charts. Next step will be to customize the HTML-generators so I can get the Tailwind classes out of the box.

Related Tutorials

Published 11 Jul - 2020
Updated 22 May - 2021

Create a reusable modal with LiveView Component

To reduce duplicity and complexity in your apps, Phoenix LiveView comes with the possibility to use reusable components. Each component can have its..

Published 04 May - 2021
Updated 05 May - 2022

How to combine Phoenix LiveView with Alpine.js

No matter how great Phoenix LiveView is, there is still some use case for sprinking some JS in your app to improve UX. For example, tabs, dropdowns,..