npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

tailwind-unwind

v0.6.2

Published

Analyze Tailwind CSS class usage patterns in React/Next.js projects

Readme

tailwind-unwind

npm version

Find repeated Tailwind classes in your React code and turn them into reusable component classes.

If you copy-paste the same className="flex items-center p-4 ..." across dozens of files, this tool helps you clean that up — without doing it by hand.

Works with React / Next.js projects. Node.js 18+.

The problem it solves

// Same utilities repeated everywhere
<div className="flex items-center justify-between p-4">...</div>
<div className="flex items-center justify-between p-4">...</div>
<div className="flex items-center justify-between p-4">...</div>

tailwind-unwind finds these duplicates and can replace them with one class:

<div className="twu-page-header">...</div>

And generates the CSS for you:

@layer components {
  .twu-page-header {
    @apply flex items-center justify-between p-4;
  }
}

Quick start

Run from your project root — no paths required (defaults: scan ., CSS output styles.css):

# 1. Quick check — extractable duplicates + apply preview
npx tailwind-unwind check

# 2. Generate CSS
npx tailwind-unwind generate

# 3. Import styles.css in globals.css, then replace in source files
npx tailwind-unwind apply --dry-run   # preview first
npx tailwind-unwind apply             # write changes

Override defaults with flags (./src, --output src/styles/components.css) or tailwind-unwind.config.json.

Try the built-in demo in this repo:

npx tailwind-unwind check ./test-project
npx tailwind-unwind generate ./test-project --output test-project/styles.css
npx tailwind-unwind apply ./test-project --output test-project/styles.css --dry-run

Install globally (optional):

npm install -g tailwind-unwind

Commands

| Command | What it does | |---------|--------------| | check | One-shot health check: extractable duplicates + apply dry-run preview. Start here. | | analyze | Detailed report of frequent class combinations. Safe — read-only. | | generate | Creates a CSS file with @layer components + @apply. Does not touch your .tsx files. | | apply | Does what generate does and rewrites matching className in source files. | | init | Generates tailwind-unwind.config.json from your project scan. |

analyze vs generate / apply

These commands use different matching strategies and default thresholds:

| | analyze | generate / apply / check | |--|-----------|--------------------------------| | Goal | Hints — frequent class subsets | Action — exact duplicate class strings | | Default --min-occurrences | 5 | 3 | | Typical output | "flex p-4" subset — 12×, subset only | twu-page-header from 8 identical full strings |

analyze helps you explore; generate / apply only extract patterns where the entire className string matches byte-for-byte (after class-order normalization).

With extractableOnly (default in config), generate / apply use the full extractable scan — not limited to the analyze top list.

check — recommended entry point

npx tailwind-unwind check

Shows extractable pattern count, top matches, and a dry-run apply preview (files to change, replacement count).

All commands show a terminal spinner in interactive mode (e.g. Scanning source files... 42/180). Disabled in CI, with --format json, or --no-progress.

For CI — fail when duplicates exceed a threshold:

npx tailwind-unwind check --fail-on-extractable 0   # fail if any extractable pattern exists
npx tailwind-unwind check --format json

Typical workflow

# Optional: create config from your project
npx tailwind-unwind init

# Quick overview + dry-run preview
npx tailwind-unwind check

# Detailed report (optional)
npx tailwind-unwind analyze --format json > report.json

# Generate CSS (from scan or saved report)
npx tailwind-unwind generate
npx tailwind-unwind generate --from-report report.json

# Preview replacements, then apply
npx tailwind-unwind apply --dry-run
npx tailwind-unwind apply --prettier

Configuration

Create tailwind-unwind.config.json manually or run init:

npx tailwind-unwind init

Also supported: .tailwind-unwindrc, tailwind-unwind.config.ts / .js.

Example — see tailwind-unwind.config.example.json.

{
  "names": {
    "flex items-center justify-between p-4": "page-header"
  },
  "analyze": { "minOccurrences": 5, "top": 10 },
  "generate": { "minOccurrences": 3, "prefix": "twu-", "output": "styles.css", "extractableOnly": true },
  "apply": { "minOccurrences": 3, "prettier": true }
}

Key options:

  • include / exclude — which files to scan
  • names — map utilities to your class names ("flex p-4""toolbar")
  • analyze / generate / apply — per-command settings (minOccurrences, top, output, …)

CLI flags override config values.

--min-occurrences

Minimum repeat count before a pattern is considered:

  • analyze — show in the frequent-combinations report (subset search). Default 5.
  • generate / apply — create a component class from exact duplicates. Default 3.

Lower the value to catch rarer duplicates: --min-occurrences 2.

  • analyze — default --max-size 5 (subset search is combinatorial; keeps large repos fast)
  • generate / apply — no maxSize cap by default; full exact duplicates of any length are extracted

Use --max-size <n> on analyze to widen subset hints, or on generate to cap extraction length.

What apply can replace

| Pattern | Supported? | |---------|------------| | className="flex p-4 bg-blue" | Yes | | className={cn('flex', 'p-4')} | Yes | | className={cn('flex p-4', isActive && 'bg-blue')} | Yes (static part only) | | className={`flex p-4 ${x}`} | Yes (static part only) | | className={buttonVariants()} | Yes (cva/tv, no arguments) | | className={({ isActive }) => isActive ? 'flex p-4' : 'flex p-2'} | Yes (per-branch exact match) | | className={({ isActive }) => isActive ? 'flex p-4 text-accent' : 'flex p-4 text-muted'} | Yes (shared prefix → component class; branch-specific classes stay) | | className={getClasses()} | No — skipped |

Parsed: cn, clsx, classnames, twMerge, cva, tv, template literals.
Class order does not matter (flex p-4 = p-4 flex).

Skipped locations are grouped by reason in the console; use --verbose-skipped for the full list.

Generated class names

Default prefix is twu- to avoid clashes with your existing styles:

| Repeated utilities | Becomes | |--------------------|---------| | flex items-center justify-between p-4 | twu-page-header | | w-full object-cover rounded-lg | twu-media-cover |

Override with --prefix app- or the names field in config.

Useful flags

| Flag | Commands | Purpose | |------|----------|---------| | --no-progress | all commands | Disable terminal spinner (auto-off in CI / --format json) | | --fail-on-extractable <n> | check | Exit 1 when extractable patterns exceed n | | --verbose-skipped | apply, check | List every skipped replacement (default: grouped summary) | | --dry-run | apply | Preview without writing files | | --prettier | apply | Format changed files with Prettier | | --format json | analyze, check, generate, apply | Output for CI / scripts | | --changed [ref] | all | Only git-changed files | | --from-report <file> | generate, apply | Use analyze JSON output | | --extractable-only | generate, apply | Only exact duplicates (default via config) | | --min-occurrences <n> | all | Repeat threshold (see above) | | --max-size <n> | all | Optional cap on classes per combination | | --config <file> | all | Custom config path | | --include / --exclude | all | Filter files by glob |

Defaults

| | analyze | check / generate / apply | |--|---------|--------------------------| | scan path | . (project root) | . | | --output | — | styles.css | | --min-occurrences | 5 | 3 | | --max-size | 5 | — (no limit) | | --prefix | — | twu- |

Config file values override CLI defaults; explicit flags override config.

Programmatic API

import {
  checkCommand,
  analyzeCommand,
  generateCommand,
  applyCommand,
} from 'tailwind-unwind';

await checkCommand('.', { output: 'styles.css' });
await analyzeCommand('.', { format: 'json', extractableMinOccurrences: 3 });

Full exports: walkSourceFiles, parseFile, findRepeatedClassSets, buildComponents, loadCommandOptions, and more.

CI

In your app repo — gate PRs on duplicate Tailwind patterns:

npx tailwind-unwind check --fail-on-extractable 0 --format json

GitHub Actions composite action — see action.yml:

- uses: AVPletnev/[email protected]
  with:
    command: check
    format: json
    args: --fail-on-extractable 0

Development

git clone https://github.com/AVPletnev/tailwind-unwind.git
cd tailwind-unwind && npm install && npm run build && npm test

CI in this repo also runs check against test-project as a smoke test. The test-project/ folder is a self-contained demo; test/fixtures/ holds minimal parser unit-test samples.

License

MIT — see LICENSE.