CanonicalTailwind

CIHex.pmHex Docs

Canonicalizes Tailwind CSS utility classes in HEEx templates via mix format.

Delegates to the tailwindcss CLI's canonicalize --stream subcommand, which sorts classes, normalizes utilities to their canonical form, and collapses duplicates. Powered by the same Tailwind CSS engine as the Prettier plugin.

- mr-4 custom-btn flex ml-[1rem] flex
+ custom-btn mx-4 flex

Unknown classes are preserved and sorted to the front.

Requirements

The :tailwind package's default CLI version may lag this requirement. If startup reports an older Tailwind version, set config :tailwind, version: "4.2.2" (or newer) and run mix tailwind.install.

Setup

Add canonical_tailwind to your dependencies:

# mix.exs
defp deps do
[
{:canonical_tailwind, "~> 0.2.0", only: [:dev, :test], runtime: false}
]
end

Then in .formatter.exs, add attribute_formatters alongside your existing HEEx formatter plugin:

# .formatter.exs
[
plugins: [Phoenix.LiveView.HTMLFormatter],
attribute_formatters: %{class: CanonicalTailwind},
# ...
]

Now mix format automatically canonicalizes Tailwind classes in class attributes, processing only files changed since its last run.

Editor usage

If your editor formats via an LSP (like Expert or ElixirLS), the first format-on-save after starting the editor will take a few seconds while the tailwindcss CLI starts up. Subsequent saves are near instant.

Configuration

If you have the :tailwind hex package set up with a single profile (the default for Phoenix projects), everything is detected automatically — no configuration needed.

Multiple tailwind profiles

If your project has multiple tailwind profiles, specify which one to use:

# .formatter.exs
[
plugins: [Phoenix.LiveView.HTMLFormatter],
attribute_formatters: %{class: CanonicalTailwind},
canonical_tailwind: [profile: :app],
# ...
]

Timeout

The tailwindcss CLI needs to initialize before it can respond to its first request. On slower CI machines or larger projects, this can exceed the default timeout of 30 seconds. Adjust with :timeout:

# .formatter.exs
[
plugins: [Phoenix.LiveView.HTMLFormatter],
attribute_formatters: %{class: CanonicalTailwind},
canonical_tailwind: [timeout: 60_000],
]

Custom binary

If you're not using the :tailwind hex package, provide the path to the CLI binary and optionally a CSS entrypoint. The CLI needs your CSS entrypoint to resolve @theme customizations and plugins when determining canonical forms.

# .formatter.exs
[
plugins: [Phoenix.LiveView.HTMLFormatter],
attribute_formatters: %{class: CanonicalTailwind},
canonical_tailwind: [
binary: "node_modules/.bin/tailwindcss",
input: "css/app.css",
cd: Path.expand("assets", __DIR__)
],
# ...
]

Other attributes

The attribute_formatters key maps attribute names to formatters, so any attribute holding Tailwind classes can be canonicalized. Register each one the same way as class:

# .formatter.exs
[
plugins: [Phoenix.LiveView.HTMLFormatter],
attribute_formatters: %{class: CanonicalTailwind, "data-class": CanonicalTailwind},
# ...
]

Multiple builds in one run

A single mix format can span apps or directories that resolve to different configurations: an umbrella whose apps use different tailwind profiles, or a project using subdirectories with per-directory .formatter.exs files. Each distinct configuration gets its own warm tailwindcss CLI, so they coexist in one run without conflicting.

Background

Built by a contributor to TailwindFormatter, attribute_formatters, and canonicalize --stream.