View Source Tailwind Setup

Beacon has built-in TailwindCSS support, any page can use its classes out of the box and a stylesheet will be automatically generated and served.

A default and simple configuration that works with Phoenix and Beacon is already bundled in the Beacon package, so you can skip this guide if that suits your needs.

Otherwise, keep reading to learn how to set up a custom configuration with more advanced features like plugins.

Note that the Tailwind configuration must respect some constraints to work properly with Beacon, so if you want to reuse an existing configuration, make sure to follow the steps below and make the necessary adjustments. It might be a good idea to keep separated configs, one for your application and another one for Beacon sites, and reuse parts that are common between them.

Objective

Make sure the proper Tailwind version is installed, create a valid Tailwind config in the ESM format, then bundle everything together in a single module.

Constraints

Since Beacon uses the same configuration to generate stylesheets for your sites and also to preview pages on the Visual Editor in the browser, that configuration must respect some constraints to work properly in both environments:

  • Use the ESM format
  • Can't call node APIs

In the steps below you'll learn how to make the neccessary adjustments.

Steps

  • Install Tailwind v3.3.0 or higher
  • Install Esbuild
  • ESM format
  • Remove node APIs
  • Heroicons
  • Install plugins
  • Bundle the config
  • Use the config in your site configuration

Let's go through each one to set up Tailwind properly for your sites.

Tailwind v3.3.0 or higher

Any recent Phoenix application should have the tailwind library already installed and updated but let's double check by executing:

mix run -e "IO.inspect Tailwind.bin_version()"

If it fails or the version is lower than 3.3.0 then follow the tailwind install guide to get it installed or updated. It's important to install a recent Tailwind version higher than 3.3.0

Esbuild

Similar to Tailwind, any recent Phoenix application should have it installed already but let's check by executing:

mix run -e "IO.inspect Esbuild.bin_version()"

If it fails then follow the esbuild install guide to get it installed. Any recent version that is installed should work just fine.

ESM format

Beacon expects a config file in the ESM format, ie: one that has default export instead of module.exports.

Most likely the existing config assets/tailwind.config.js was created in the CommonJS format, so given a file like this:

module.exports = {
  theme: {
    colors: {
      'blue': '#1fb6ff'
    }
  }
}

Replace with the following syntax to have a valid ESM config:

export default {
  theme: {
    colors: {
      'blue': '#1fb6ff'
    }
  }
}

More info at https://tailwindcss.com/blog/tailwindcss-v3-3#esm-and-type-script-support

Remove node APIs

The default tailwind.config.js created by the Phoenix installer will require the fs and path modules to load Heroicons in your application, the problem is that those modules are not available in the browser, so we can't use them.

You have 2 options. Either remove those requires and the code using that module if you're not using Heroicons or not planning to use them, or the other option is to create a new config file only for Beacon, that doesn't require those APIs.

See the Heroicons section below for more information regarding loading icons for Beacon pages.

Heroicons

Heroicons are bundled by default in the .heroicon component, see the Heroicons guide for more information.

Install plugins

The default config generated by Phoenix imports the @tailwindcss/forms plugin so we need to install it first. Execute in the root of your project:

npm install --prefix assets --save @tailwindcss/forms

Bundled config

We'll change 3 files to make it work:

  1. config/config.exs

Open the file config/config.exs, find the :esbuild config, and add a new tailwind_bundle that will look like this:

config :esbuild,
  version: "0.23.0",
  my_app: [
    # omitted for brevity
  ],
  # add this block
  tailwind_bundle: [
    args: ~w(tailwind.config.js --bundle --format=esm --target=es2020 --outfile=../priv/tailwind.config.bundle.js),
    cd: Path.expand("../assets", __DIR__),
    env: %{"NODE_PATH" => Path.expand("../deps", __DIR__)}
  ]

Change the path to the tailwind.config.js file if you're using a different one.

  1. config/dev.exs

Open the file config/dev.exs, find the :watchers key in the endpoint config, and add a new tailwind_bundle that will look like this:

config :my_app, MyAppWeb.Endpoint,
  # omitted for brevity
  watchers: [
    esbuild: {Esbuild, :install_and_run, [:my_app, ~w(--sourcemap=inline --watch)]},
    esbuild: {Esbuild, :install_and_run, [:tailwind_bundle, ~w(--watch)]}, # <-- add this line
    tailwind: {Tailwind, :install_and_run, [:my_app, ~w(--watch)]}
  ]
  1. mix.exs

In the list of aliases, add the following command in both "assets.build" and "assets.deploy":

"esbuild tailwind_bundle"

It will look like this:

defp aliases do
  [
    # omitted for brevity
    "assets.build": ["tailwind my_app", "esbuild my_app", "esbuild tailwind_bundle"],
    "assets.deploy": [
      "tailwind my_app --minify",
      "esbuild my_app --minify",
      "esbuild tailwind_bundle --minify",
      "phx.digest"
    ]
  ]
end

Site Configuration

Note that if you're setting up the environment for the first time and have no site created yet, for example if you're following the "Your first site" or the "Create a Blog" guide, you won't have any site configuration to update. In this case, you can skip this step and come back to it later after executing the beacon.install command.

Open the file lib/my_app/application.ex (replace my_app with your actual application name), find the configuration of the site you'll be using this Tailwind config and add the tailwind_config key pointing to the bundled file:

tailwind_config: Path.join(Application.app_dir(:my_app, "priv"), "tailwind.config.bundle.js"),

It will look somewhat like this:

@impl true
def start(_type, _args) do
  children = [
    # omitted for brevity
    {Beacon,
     sites: [
       [
         site: :my_site,
         repo: MyApp.Repo,
         endpoint: MyAppWeb.Endpoint,
         router: MyAppWeb.Router,
         tailwind_config: Path.join(Application.app_dir(:my_app, "priv"), "tailwind.config.bundle.js") # <-- add this line
       ]
     ]},
    MyAppWeb.Endpoint
  ]

  # omitted for brevity
end

Remember to replace my_app with the actual name of your application.

Now the bundled Tailwind config will be used by Beacon to generate the styles for your site and to create pages using the Visual editor, and the same config will be used to deploy your site.