Psst. It would be super cool if you could try the new Phoenix Boilerplate

Try now →


View on Github

Add Tailwind HTML Generators in Phoenix


Let’s say that you have a project set up with Tailwind. Or even if you dont, let say you just want to customise the code build in HTML generators generate.

But since I do use Tailwind, I want to call my html generator:

mix tailwind.gen.html .. instead of mix phx.gen.html ..


A good place to start is to copy the actual generator from the files that was created when the project was generated.

mkdir -p lib/mix/tasks
cp deps/phoenix/lib/mix/tasks/phx.gen.html.ex lib/mix/tasks/tailwind.gen.html.ex

Then open the file and rename the module to


And in the file, we also need to change all instances of phx to tailwind.

AS you can see in the copy_new_files method, it expects the new files to be used of the generator to be in the folder: priv/templates/tailwind.gen.html.

THe commands to create the folder and copy the generator files over are:

mkdir -p priv/templates/tailwind.gen.html
cp -a deps/phoenix/priv/templates/phx.gen.html/. priv/templates/tailwind.gen.html

If you managed to to this without mistakes you shoule be able to run:

mix compile && mix help | grep 'tailwind'

If everything works, you should see something like:


Next step is little of a hot topic. I prefer extracting some of the most atomic html elements to the css file and not only use the classes inline in the DOM.

These would be cards, inputs and buttons. And the reason is that its important that they look similar across the site.

I also am of the opinion that the class names to use would be Bootstrap compatible. So I would name it .card, .btn .btn-primary, form-control etc. That makes it simple for other that are most used to Bootstrap to navigate the code.

So, once more I need to edit lib/mix/tasks/tailwind.gen.html.ex inside the defp inputs and append the class in each case statement.

, class: "form-control"

Then when it comes to the templates, I can go through them and edit them to whatever I want. In priv/templates/tailwind.gen.html/show.html.eex I want to but the content in a card-div with a header.

So the original code:

<h1>Show <%= schema.human_singular %></h1>

<%= for {k, _} <- schema.attrs do %>
    <strong><%= Phoenix.Naming.humanize(Atom.to_string(k)) %>:</strong>
    <%%= @<%= schema.singular %>.<%= k %> %>
<% end %>

<span><%%= link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, @<%= schema.singular %>) %></span>
<span><%%= link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index) %></span>

And the updated template after the changes:

<div class="card">
  <div class="card-header flex">
    <h5 class="mb-0 flex-1">Show <%= schema.human_singular %></h5>
    <span class="text-sm mr-1"><%%= link "Edit", to: Routes.<%= schema.route_helper %>_path(@conn, :edit, @<%= schema.singular %>) %></span>
    <span class="text-sm"><%%= link "Back", to: Routes.<%= schema.route_helper %>_path(@conn, :index) %></span>
  <div class="card-body">
<%= for {k, _} <- schema.attrs do %>
      <li class="mb-2">
        <strong><%= Phoenix.Naming.humanize(Atom.to_string(k)) %>:</strong>
        <%%= @<%= schema.singular %>.<%= k %> %>
<% end %>

Since I now references some classes, I need to add them in assets/css/app.css. The custom css needs to go between components and utilities

@tailwind base;
@tailwind components;
// Add custom css here ...
@tailwind utilities;

The good thing is that you can still use Tailwind classes like this:

.btn {
  @apply inline-block font-normal text-center px-3 py-2 leading-normal text-base rounded cursor-pointer;

.btn-primary {
  @apply text-white bg-blue-600;

.btn-primary:hover {
  @apply text-white bg-blue-700

Related Tutorials

Published 01 Apr

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 31 Mar

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,..