Back to Blog

Stop Fixing Tailwind Canonical Class Warnings by Hand

Adeel Imran
Adeel Imran

If you use Tailwind CSS with VS Code, the Tailwind IntelliSense extension has probably been nagging you. You write [&>[role=checkbox]]:translate-y-[2px] and a little hint appears telling you to change it to *:[[role=checkbox]]:translate-y-0.5. You dismiss it. You move on. You open the next file and it happens again.

Most developers either ignore these hints entirely or fix them one by one as they stumble across them. Both approaches are wrong. There's a single command that does all of it across your entire codebase at once, and most people have never run it.


What is a "canonical class" anyway?

Tailwind evolves. What was the idiomatic way to express something in v3 might have a cleaner, more concise equivalent in v4. Canonical classes are the official, preferred representation of a utility in the current version of Tailwind.

The Tailwind IntelliSense extension picks these up and surfaces them as suggestions. It's not just a stylistic preference. Canonical syntax is what the docs show, what your team will expect when they look up a utility, and what's going to keep working as Tailwind continues to evolve.

Here's a concrete example. Before:

<div class="[&>[role=checkbox]]:translate-y-[2px]">
  <!-- content -->
</div>

After canonicalization:

<div class="*:[[role=checkbox]]:translate-y-0.5">
  <!-- content -->
</div>

Two things happened there. The *: universal selector variant replaced the [&>...] arbitrary variant, which is cleaner and more idiomatic in modern Tailwind. And [2px] became 0.5, because 0.5 is the canonical way to express 2px in Tailwind's spacing scale. You get consistency with the rest of your codebase and alignment with the official docs.


The Tailwind Upgrade CLI

Tailwind ships an official upgrade tool, and it handles canonical class conversion as a side effect:

npx @tailwindcss/upgrade

That's it. Run it from the root of your project and it will scan your entire codebase, find every non-canonical class, and rewrite them. You get a clean diff you can review before committing.

It also handles actual Tailwind version upgrades, but you don't need to be upgrading versions to benefit from it. Just want canonical classes cleaned up across an existing v4 project? Run it. It's safe. It only rewrites the class syntax, not the logic around it.

Works with all the major package managers:

# npm
npx @tailwindcss/upgrade

# pnpm
pnpx @tailwindcss/upgrade

# bun
bunx @tailwindcss/upgrade

# yarn
yarn dlx @tailwindcss/upgrade

When should you actually run this?

The obvious times are after a Tailwind version upgrade or when you're onboarding to a legacy project with older Tailwind code. But there are two scenarios I think developers consistently underestimate.

After using component generators. Tools like shadcn/ui generate Tailwind classes based on their own internal logic. That logic doesn't always produce canonical output. Run the upgrade CLI after pulling in new shadcn components and you'll clean up whatever non-canonical patterns slipped in.

After AI-generated code. GitHub Copilot, Claude, ChatGPT: they all write Tailwind. And they all write it based on patterns in their training data, which spans multiple Tailwind versions. The output is not always canonical. If you're using AI heavily in your codebase (and you are), make running the upgrade CLI a regular habit, not a one-off thing.

I run it periodically the same way I run dependency updates. Not because something broke, but because keeping the codebase modern now is cheaper than doing it in bulk later.


Bonus: Pair it with class sorting

If you're going to enforce canonical syntax, you might as well enforce class order too. The official Prettier plugin handles that:

npm install -D prettier-plugin-tailwindcss

Add it to your Prettier config:

{
  "plugins": ["prettier-plugin-tailwindcss"]
}

Or, if you're on Biome, the useSortedClasses rule in the nursery ruleset does the same:

{
  "linter": {
    "rules": {
      "nursery": {
        "useSortedClasses": "error"
      }
    }
  }
}

Canonical syntax via the upgrade CLI plus sorted classes via Prettier or Biome is the full stack for clean Tailwind code. One handles what classes you write, the other handles what order they appear in.


Bonus 2: Enforce it through linting

If you want canonical class violations to fail your CI rather than just show up as editor hints, there are ESLint plugins for that too.

eslint-plugin-better-tailwindcss has an upcoming v4 with an enforce-canonical-classes rule. There are also a couple of dedicated plugins:

And the widely-used eslint-plugin-tailwindcss has an open feature request for canonical class enforcement. If that lands, it'll make this a zero-friction default for most projects.


The full setup, summarized

Here's what a well-configured Tailwind project looks like for class hygiene:

LayerToolWhat it does
One-time fix@tailwindcss/upgradeRewrites all non-canonical classes
On savePrettier + tailwindcss pluginSorts class order automatically
In CIESLint canonical pluginFails builds with non-canonical code

You don't need all three. Even just running the upgrade CLI occasionally gets you 80% of the way there. But if you want the whole thing locked down without thinking about it, that's the stack.


The next time you see VS Code suggesting a canonical class fix and you're tempted to just update that one instance and move on: don't. Run npx @tailwindcss/upgrade, review the diff, and move on with your life. It takes thirty seconds and handles your entire project at once.

There's no good reason to do this manually anymore.


This post was inspired by Jimmy Guzman Moreno's write-up on the same topic. Worth a read.