rustywind: a Rust CLI that sorts Tailwind class lists and fails your build
CLI for organizing Tailwind CSS classes
At a glance
- What is it?
- rustywind rewrites class attributes in a chosen order across HTML, JSX, Svelte, Astro and a dozen template languages, and it can report unsorted files as a CI failure. The interesting part is how conservative it is with template syntax it does not recognise.
- Who is it for?
- Use rustywind if your team wants one enforced class order across mixed template languages and wants the check to run in CI as a separate binary rather than inside a formatter. Skip it if you rely on Tailwind's own class ordering, or if your templates are dense with dynamic class expressions and you would rather the tool did nothing there.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 7 days ago.
- What is it written in?
- Mainly Rust, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 20, 2026, and from our analysis. They are not legal advice.
Editorial analysis
RustyWind rewrites class attributes, not whole files
rustywind is a command line tool for organising Tailwind CSS classes, written in Rust and distributed through npm, crates.io, Homebrew and a GitHub release script. The scope is narrow on purpose: it finds class attributes, sorts the class list inside them, and leaves the rest of the file alone. That matters for diff noise, because a formatter that reflows a whole template produces reviews nobody reads. The README is explicit that a class list is only rewritten where the tool can positively identify it: static quoted class and className attributes are sorted, while program text, comments and dynamic attributes in JSX and TSX are preserved. The same conservative stance shows up in Astro, where frontmatter, expressions, dynamic class attributes and class:list directives are left alone while static quoted class attributes are sorted. Duplicate classes are removed unless you pass --allow-duplicates, and --preserve-whitespace keeps the original spacing, including multiline class lists, so a reformat does not undo your manual line breaks.
Language profiles decide what a class list even looks like
RustyWind infers the template syntax from the file extension, and that inference is where the tool earns its keep. Supported profiles are HTML, JSX, TSX, Svelte, Astro, Django, Jinja, Twig, Liquid, Handlebars, ERB, EJS, PHP, Blade, Lit and Ruby, and you can override the guess with --language, for example when a file that holds a Svelte component is named Component.html. A file whose extension is not recognized gets conservative legacy-compatible extraction, meaning simple static class attributes are still sorted while template-looking attributes are left unchanged. The README also points out a trap that catches people: a plain .html extension does not tell you which template engine produced the file, so attributes containing template syntax stay untouched unless you select a profile such as --language django. When reading a piped file, you can pass --stdin-filename so the tool can infer the syntax from the name you give it.
Four modes, and only one of them touches your files
The CLI exposes a small set of modes, and the built-in help lists them plainly:
rustywind . --dry-runThat prints the sorted content to the terminal without writing anything. To rewrite files in place:
rustywind --write .To fail a build when class lists are not in order, which is the mode you want in CI:
rustywind --check-formatted .And to work on content that never touches disk:
echo "<FILE CONTENTS>" | rustywind --stdinThe check mode exits with 1 when it finds unformatted files, which is what makes it usable as a pipeline gate rather than a suggestion. The path argument accepts a file or a directory, and multiple paths are allowed. In CI you would run the check form and treat a non-zero exit as a failure, and locally you would use the dry run to see what would change before letting --write touch a branch.
JSON output turns the sorter into a tool other programs can use
Every mode accepts --json, which replaces the human output with a single machine-readable document:
rustywind --check-formatted --json .Two details make this more than a print switch. First, bare rustywind --json . behaves like --dry-run --json and never writes files, so a JSON invocation cannot accidentally modify a working tree. Second, unlike the human dry run, the dry-run JSON document carries the full formatted content of every file that would change under proposed_changes, so an automation step can apply the result without running the tool a second time. Each document reports an ok field, which is true when formatting is clean and nothing failed, and for --write only when every write succeeded, plus an errors array of path and message entries for unreadable files or a failed directory walk. Exit codes follow the same logic: 1 for unformatted files under --check-formatted, 1 in any JSON mode when errors is non-empty, and 0 for dry-run and stdin JSON even when changes are merely proposed.
Sort order can come from your CSS, a Vite URL or a JSON file
The default order is Tailwind's own, but three options let you override it. --output-css-file points at the Tailwind generated CSS file and the order of classes in that file becomes the sort order, which keeps the source order aligned with what the framework actually emits. --vite-css does the same against a CSS file served by a Vite dev server, and the README marks that option as experimental and liable to be removed, with --skip-ssl-verification available for local servers using self-signed certificates. Third, --config-file takes a JSON document with a top level sortOrder array listing classes in the order you want them, and the README notes the config file currently supports only that one property. Two smaller switches matter in practice: -p, --tailwind-prefix tells the sorter about a prefix such as tw for tw: classes, and --custom-regex lets you point at your own pattern when class lists live somewhere the built-in extraction does not look.
A Cargo workspace, a scratch image and an npm wrapper
The repository is a Rust workspace with four members, rustywind-cli, rustywind-core, rustywind-vite and xtask, and rustywind-cli is the default member. The release profile is tuned for a small binary, with fat link time optimization, codegen-units set to 1, opt-level 3 and symbols stripped. The Dockerfile compiles inside a Rust Alpine builder and copies the binary into a scratch image, so the running container has no shell and no package manager, and it runs as a non-root user. Installation is offered through several channels at once, npm and yarn globally, crates.io with cargo install or cargo binstall, a Homebrew tap, a GitHub release installer script, a direct download, and a Docker image that mounts your working directory. The npm/ directory holds a wrapper package so the tool can sit in a JavaScript toolchain, and a justfile provides the developer commands such as cargo fmt and cargo clippy with warnings denied.
Fast releases on master, with a permissive licence
The project is on master, is not archived, and its last push was on 2026-10-15. Releases are frequent and versioned, with v0.26.0 on 2026-07-21, v0.27.0 on 2026-08-10 and v0.28.0 on 2026-08-25. The version is still below 1.0, so treat the CLI flags as something that can change, and the experimental label on --vite-css is a concrete example of a flag that may disappear. The licence is Apache-2.0, declared in the workspace Cargo.toml and in the LICENSE file, which is the permissive standard most teams are comfortable shipping internally. Nothing in the licence text is unusual enough to need special handling, but a commercial team that wants to wrap rustywind inside a larger product should still read the licence and the CHANGELOG.md and RELEASE.md files in the tree before depending on it.
Editorial conclusion
Use rustywind if your team wants one enforced class order across mixed template languages and wants the check to run in CI as a separate binary rather than inside a formatter. Skip it if you rely on Tailwind's own class ordering, or if your templates are dense with dynamic class expressions and you would rather the tool did nothing there. Verify first which sort order you want by pointing --output-css-file at your generated stylesheet, run --dry-run on one directory to inspect proposed_changes, and confirm that --language matches the template engine of every file you intend to enforce, since rustywind leaves template-looking attributes untouched when it cannot identify the engine.
Frequently asked questions
How do I install rustywind?
The README lists several channels: yarn global add rustywind or npm install -g rustywind, brew install avencera/tap/rustywind, cargo install rustywind or cargo binstall rustywind, a GitHub release install script, or the Docker image avencera/rustywind:latest. The binary is also downloadable from the GitHub releases page.
How do I make rustywind fail a CI pipeline on unsorted classes?
Run the check mode, which exits with 1 when it finds unformatted files. In JSON mode the document reports an ok field and an errors array, and the exit code is also 1 in any JSON mode when errors is not empty.
Which template languages does rustywind understand?
The README lists HTML, JSX, TSX, Svelte, Astro, Django, Jinja, Twig, Liquid, Handlebars, ERB, EJS, PHP, Blade, Lit and Ruby. Files with unrecognized extensions get conservative legacy-compatible extraction, so only simple static class attributes are sorted.
Can rustywind sort classes in the order my Tailwind CSS file uses?
Yes. The --output-css-file option derives the sort order from the order classes appear in a given CSS file, and --vite-css does the same for a stylesheet served by Vite, though the README marks the Vite option as experimental. You can also set the order yourself in a JSON config file with a top level sortOrder array.
Does rustywind modify files when I run it with --json?
No. Bare rustywind --json . behaves like --dry-run --json and never writes files. Writes only happen with --write, and in JSON mode the proposed_changes entry holds the full formatted content so another tool can apply it.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/avencera-rustywind)Was this useful?