Check and automatically fix Tailwind CSS utility class order in JSX/TSX files.
twaz enforces a consistent, readable order for className, class, and cn() arguments. It scans source files, reports violations, and can reorder classes in place with --fix.
- Installation
- Usage
- What gets scanned
- Tailwind class order rules
- Precedence overview
- 1. Position anchor
- 2. Position offsets
- 3. Self & group
- 4. Element
- 5. Margin & padding
- 6. Width & height
- 7. Display
- 8. Text size
- 9. Font
- 10. Text color
- 11. Background & fill color
- 12. Variant modifiers
- 13. Transition
- 14. Border
- 15. Rounding
- 16. Shadow
- 17. Truncate & overflow
- 18. Children (grid & flex)
- 19. End
- Examples
- How violations are detected
- Execution flow
- In conclusion
npm install -D twazOr run without installing:
npx twaz src# Check class order (default: current directory)
twaz
# Check a specific directory or file
twaz src
twaz src/components/Button.tsx
# Automatically fix class order in place
twaz --fix srcimport { checkClassString, sortClassString, runScan } from "twaz";
checkClassString("bg-muted text-sm absolute");
// → [{ token: "bg-muted", group: "background & fill color", after: "text size" }, ...]
sortClassString("bg-muted text-sm absolute top-0");
// → "absolute top-0 text-sm bg-muted"
const { violations } = runScan(["src"], { fix: false });npm install
npm run dev # run CLI with tsx (debug)
npm run fix # run CLI with --fix via tsx
npm run typecheck # TypeScript check
npm run build # production build with tsdownBy default, twaz scans .tsx and .jsx files. It looks for class strings in:
className="..."/className='...'className={...}/className={"..."}class="..."cn("...")/classNames("...")
Directories node_modules, dist, and .git are skipped.
When writing or editing className, class, or cn() arguments, order utility classes in this sequence. Separate groups with a single space. Keep variant prefixes attached to each utility (e.g. hover:bg-primary, not hover: bg-primary).
Lower numbers sort earlier (left). Higher numbers sort later (right).
| # | Group | Examples |
|---|---|---|
| 1 | Position anchor | relative, absolute, fixed, sticky, static |
| 2 | Position offsets | inset-*, top-*, right-*, bottom-*, left-* |
| 3 | Self & group | self-*, group, group/name |
| 4 | Element | shrink-*, grow-*, select-*, whitespace-*, compress-zero |
| 5 | Margin & padding | m-*, p-*, and axis variants |
| 6 | Width & height | w-*, h-*, min-*, max-*, size-*, aspect-* |
| 7 | Display | block, inline, hidden, visible |
| 8 | Text size | text-xs, text-sm, text-base, text-lg, etc. |
| 9 | Font | font-* (e.g. font-medium, font-mono) |
| 10 | Text color | text-red-500, text-muted-foreground, text-center |
| 11 | Background & fill color | bg-*, fill-*, stroke-*, gradients, opacity-* |
| 12 | Variant modifiers | hover:, focus:, disabled:, aria-*:, data-*:, dark:, md: |
| 13 | Transition | transition-*, duration-*, animate-* |
| 14 | Border | border, border-*, outline-*, ring-*, divide-* |
| 15 | Rounding | rounded-* |
| 16 | Shadow | shadow-* |
| 17 | Truncate & overflow | truncate, overflow-*, text-ellipsis |
| 18 | Children (grid & flex) | grid-*, flex, gap-*, items-*, justify-*, etc. |
| 19 | End | cursor-*, pointer-events-*, z-* (always last) |
Within the same group, the original relative order is preserved (stable sort).
Positioning mode comes first — before any offset values.
relative | absolute | fixed | sticky | static
Variant-prefixed anchors (e.g. md:absolute) are treated as variant modifiers (group 12) and sort after base color utilities.
Offset utilities immediately follow the position anchor.
inset-* | top-* | right-* | bottom-* | left-*
Self-alignment and group markers for child state styling.
self-* | group | group/accordion-trigger
Named groups like group/accordion-trigger are recognized as group utilities, not variant prefixes.
Intrinsic element behavior — flex item sizing, text selection, whitespace.
shrink-* | grow-* | basis-* | select-* | whitespace-* | compress-zero
Spacing around the element. Unprefixed only — responsive or state-prefixed spacing (e.g. md:px-4, hover:p-2) is treated as a variant modifier (group 12).
m-* | mx-* | my-* | mt-* | mr-* | mb-* | ml-*
p-* | px-* | py-* | pt-* | pr-* | pb-* | pl-*
Box dimensions.
w-* | h-* | min-w-* | max-w-* | min-h-* | max-h-* | size-* | aspect-*
Display mode utilities.
block | inline | hidden | visible | isolate
Variant-prefixed display utilities (e.g. md:hidden) sort as variant modifiers.
Font size tokens only — not color or alignment.
text-xs | text-sm | text-base | text-lg | text-xl | text-2xl … text-9xl
Font family, weight, and related typography — after text size, before color.
font-medium | font-mono | font-condensed | font-*
Text color and text-related non-size utilities. Unprefixed only.
text-red-500 | text-muted-foreground | text-background | text-center | text-left
Anything matching text-* that is not a text size token (group 8) belongs here.
Surface and decorative color. Unprefixed only.
bg-* | fill-* | stroke-* | from-* | to-* | via-* | opacity-*
accent-* | caret-* | decoration-*
Any utility with a variant prefix that was not already placed in an earlier group. This includes responsive, state, dark mode, ARIA, and data attribute variants.
hover:* | focus:* | disabled:* | aria-*:* | data-*:* | dark:* | md:* | lg:*
group-hover:* | group-data-*:* | hidden:*
Rules:
- Prefixed margin/padding, display, position, border, rounding, shadow, and children utilities land here.
- Variant-prefixed transition utilities also land here (base
transition-*without a prefix is group 13). - The
:prefix stays attached to the utility name.
Motion and animation for the element itself (unprefixed).
transition-* | duration-* | animate-*
Placed after color-defining classes so base colors are established before motion.
Borders, outlines, rings, and dividers. rounded-* is excluded (see group 15).
border | border-* | outline-* | ring-* | divide-*
Corner radius.
rounded-* | rounded-sm | rounded-md | rounded-full
Box shadows.
shadow-* | shadow-sm | shadow-md | shadow-lg
Text truncation and overflow — before children layout utilities.
truncate | overflow-* | text-ellipsis
Layout that affects children. Within this group, grid-* sorts before flex utilities when both are present.
grid | grid-* | inline-grid
flex | inline-flex | flex-*
gap-* | items-* | justify-* | content-* | place-*
order-* | col-* | row-* | space-x-* | space-y-* | list-*
Variant-prefixed children utilities (e.g. md:flex) sort as variant modifiers.
Interaction and stacking — always last.
cursor-* | pointer-events-* | z-*
// ❌ BAD
<div className="top-0 left-0 absolute bg-muted text-sm text-muted-foreground" />
// ✅ GOOD
<div className="absolute top-0 left-0 text-sm text-muted-foreground bg-muted" />// ❌ BAD
<button className="font-medium text-sm hover:bg-primary flex px-4 h-9 bg-background border rounded-md" />
// ✅ GOOD
<button className="px-4 h-9 text-sm font-medium text-foreground bg-background hover:bg-primary border rounded-md flex" />// ❌ BAD
<div className="px-3 py-2 group/accordion-trigger relative z-50 flex cursor-pointer" />
// ✅ GOOD
<div className="relative group/accordion-trigger px-3 py-2 text-sm font-medium text-muted-foreground bg-muted flex cursor-pointer z-50" />// ❌ BAD
<div className="flex gap-2 overflow-auto truncate w-full" />
// ✅ GOOD
<div className="w-full overflow-auto truncate flex gap-2" />twaz walks each class string left to right. Each token is classified into a group number. If a token's group number is lower than a token that appeared earlier, it is reported as a violation — it should have appeared earlier in the string.
Unrecognized tokens are ignored for ordering (they do not trigger violations and stay in place during --fix).
flowchart TD
Start([CLI or programmatic API]) --> Entry{Entry point}
Entry -->|CLI| ParseArgs[parseArgs]
ParseArgs -->|help| Help[Print help → exit 0]
ParseArgs -->|paths + options| RunScan[runScan]
Entry -->|API| RunScan
Entry -->|checkClassString / sortClassString| Classify[classify token]
RunScan --> CollectFiles[collectFiles]
CollectFiles --> Walk{Directory?}
Walk -->|yes| Recurse[walk — skip node_modules, dist, .git]
Walk -->|no| SingleFile[Add matching file]
Recurse --> Files[.tsx / .jsx file list]
SingleFile --> Files
Files --> FixMode{fix option?}
FixMode -->|yes| FixLoop[For each file]
FixLoop --> ReadFix[readFileSync]
ReadFix --> ExtractFix[extractClassStrings]
ExtractFix --> ApplyFixes[applyFixes]
ApplyFixes --> CheckEach{checkClassString}
CheckEach -->|violations| SortFix[sortClassString → classify]
SortFix --> WriteFile[writeFileSync in place]
CheckEach -->|ok| SkipFix[Skip]
WriteFile --> Recheck[Re-scan with scanForViolations]
SkipFix --> Recheck
Recheck --> ReportFix[printViolations / return result]
FixMode -->|no| ScanLoop[For each file]
ScanLoop --> ReadScan[readFileSync]
ReadScan --> ExtractScan[extractClassStrings]
ExtractScan --> CheckScan[checkClassString]
CheckScan --> ClassifyScan[classify each token]
ClassifyScan --> CompareOrder[Compare group numbers left → right]
CompareOrder --> Violations[Collect FileViolation records]
Violations --> ReportScan[printViolations / return result]
ReportFix --> Exit{Violations remain?}
ReportScan --> Exit
Exit -->|yes| Exit1[exit 1]
Exit -->|no| Exit0[exit 0]
twaz is an acronym standing for "Tailwind from A to Z" (referring not to alphabetical order, but to the order of values in the CSS layout model).
Happy coding!