README.md

# CanonicalTailwind

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](https://tailwindcss.com) engine as the
[Prettier plugin](https://github.com/tailwindlabs/prettier-plugin-tailwindcss).

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

Unknown classes are preserved and sorted to the front.

## Requirements

- Elixir ~> 1.18
- Phoenix LiveView ~> 1.1 (for `attribute_formatters` support)
- The `tailwindcss` CLI >= 4.2.2 (first version with `canonicalize`)

## Setup

Add `canonical_tailwind` to your dependencies:

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

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

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

Now `mix format` automatically canonicalizes Tailwind classes in
`class` attributes.

## 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 processes start up. Subsequent saves are near
instant.

## Configuration

If you have the [`:tailwind`](https://hex.pm/packages/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:

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

### Pool size

CanonicalTailwind runs a pool of `tailwindcss` CLI processes to
parallelize `mix format`. The default is 6. Smaller projects may
benefit from fewer (less startup cost), larger projects from more (up
to your CPU core count).

```elixir
# .formatter.exs
[
  plugins: [Phoenix.LiveView.HTMLFormatter],
  attribute_formatters: %{class: CanonicalTailwind},
  canonical_tailwind: [pool_size: 3],
]
```

### 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`:

```elixir
# .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.

- **`:binary`** — path to the `tailwindcss` executable, relative to
  `:cd`
- **`:cd`** — working directory for the CLI process (defaults to the
  project root)
- **`:input`** — CSS entrypoint, relative to `:cd`

```elixir
# .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__)
  ],
  # ...
]
```

## Background

Built by a contributor to
[TailwindFormatter](https://github.com/100phlecs/tailwind_formatter/commits?author=aptinio),
[`attribute_formatters`](https://github.com/phoenixframework/phoenix_live_view/pull/3781),
and
[`canonicalize --stream`](https://github.com/tailwindlabs/tailwindcss/pull/19796).