@lunawerx/normwind · v3.7.0 · node 20+
Your Tailwind classes are longer than Tailwind needs them to be.
A zero-config CLI and GitHub Action that finds bloated utility strings and rewrites them into their short, canonical form. No rules file, no sort order, no opinions imposed on your repo.
run with --fix to rewrite
// at a glance
Two kinds of noise, collapsed.
Verbose utility combinations, and arbitrary values that Tailwind already has a name for. Both are found by an audit and both can be rewritten for you.
Also gap-x-4 gap-y-4, top-0 bottom-0, items-start justify-items-start, self-end justify-self-end, and the rest of Tailwind's shorthand families.
// why you can trust it
NormWind doesn't invent mappings.
Every "after" is something Tailwind itself considers equivalent to the "before". There is no heuristic and no house style in the middle.
-
Shorthand groups come from
eslint-plugin-tailwindcss's utility-group definitions. -
Canonical values come from Tailwind's own
designSystem.canonicalizeCandidatesengine. - Your Tailwind wins. It resolves the Tailwind install in the scanned project first, so fixes match the version that will actually build it.
- Nothing is written into your repo. The bundled canonical snapshot makes the first run fast and deterministic, and stays inside the package.
On Tailwind 4.0, which predates the canonicalization API, NormWind still audits and fixes shorthand groups and deliberately leaves arbitrary values alone rather than applying newer semantics to an older engine.
<!-- before -->
<div class="px-4 py-4 mt-3 mb-3
rounded-[24px] w-6 h-6">
<!-- after: normwind --fix -->
<div class="p-4 my-3
rounded-3xl size-6">
// fix modes
Audit it, preview it, or let it rewrite.
Start read-only. Widen the blast radius only when you want to.
normwind
Audit the current project and report every finding with its file and line. Exits 1 when there is anything to clean up, so it works as a gate without any extra flags.
--fix
The safe default. Rewrites .vue files, then re-runs the audit so you see what is left. Per-file fault isolation means one unparseable file cannot take the run down with it.
--fixall
The whole codebase: Vue, JS, MJS, TS, JSX, and TSX. Same transforms, wider reach, for when you want the cleanup done in one pass.
--dry-run
Pair it with either fix mode to see exactly which files would be rewritten. Nothing touches disk.
--json
Machine-readable findings for CI dashboards, custom reporters, or anything else that would rather not parse a terminal.
Scope it
Pass a folder or a glob: normwind src, or normwind "apps/web/**/*.{tsx,ts}". With no arguments it scans the project and skips node_modules, dist, and the usual build scratch.
// in ci
Utility bloat stops landing on main.
There is a first-party GitHub Action that annotates the pull request inline and fails it when findings exist. On any other provider it is one command, because the exit code already says everything.
- Inline annotations land on the exact class string, not in a log you have to go read.
- Deterministic: the bundled snapshot means no cold-boot Tailwind compile on every run.
- Bounded: a single run refuses to push more than 1,000 unique cache misses through the live canonicalizer, so scanning untrusted code cannot balloon into an out-of-memory crash.
name: NormWind
on:
pull_request:
push:
branches: [main]
permissions:
contents: read
jobs:
tailwind-audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: LunarWerxs/NormWind@v3
// one command
You don't have to install it to try it.
Point it at the project and read the report. Nothing is written unless you ask for a fix.