An open-source, zero-dependency Tailwind CSS auto-classer for Node.js. TailClass scans an entire site or web application for repeated Tailwind utility layouts, assigns smart semantic names based on both what elements look like and where they live in the DOM (.navbar, .navbar-brand, .nav-link, .btn-primary, .btn-outline, .card, .grid-3col, .badge-success, .form-input, .modal-dialog, .site-footer, …), generates a centralized stylesheet (tailclass.css), and rewrites your templates so you can update styles across your entire site from one place.
Don't want automatic names on certain patterns? Run Class List Mode (tailclass list) to audit every utility and repeated layout across your codebase — complete with class counts, occurrence counts, and exact file/line locations — and type your own names into an auto-generated tailclass.map.json dictionary.
Note
TailClass understands Tailwind CSS v3 and v4 utilities, variant stacks (dark:md:hover:…), arbitrary values (grid-cols-[240px_1fr]), negative & !important modifiers, and canonicalizes token order — so "px-4 py-2 flex items-center" and "flex items-center py-2 px-4" are automatically recognized as the exact same layout. Non-@apply markers (group, peer, dark) and custom JS/BEM hooks (js-*) are preserved inline automatically so nothing breaks at build time.
Before (index.html, pricing.html, contact.html):
<nav class="sticky top-0 z-50 flex items-center justify-between border-b border-slate-200 bg-white/90 px-6 py-4 backdrop-blur"> <a href="/" class="text-xl font-bold tracking-tight text-slate-900">Infinitode</a> <ul class="hidden md:flex items-center gap-6"> <li><a href="/" class="text-sm font-medium text-slate-600 transition hover:text-indigo-600">Home</a></li> <li><a href="/pricing" class="text-sm font-medium text-slate-600 transition hover:text-indigo-600">Pricing</a></li> </ul> <a href="/signup" class="inline-flex items-center justify-center rounded-lg bg-indigo-600 px-4 py-2.5 text-sm font-semibold text-white shadow-sm transition hover:bg-indigo-500"> Get Started </a> </nav> <!-- Superset card with extra ring utilities on the featured plan --> <div class="group js-plan rounded-xl border border-slate-200 bg-white p-6 shadow-sm ring-2 ring-indigo-600"> ... </div>
After running npx tailclass apply ./src:
<nav class="navbar"> <a href="/" class="navbar-brand">Infinitode</a> <ul class="nav-menu"> <li><a href="/" class="nav-link">Home</a></li> <li><a href="/pricing" class="nav-link">Pricing</a></li> </ul> <a href="/signup" class="btn-primary"> Get Started </a> </nav> <!-- Shared `.card` extracted; `ring-2 ring-indigo-600` + inline `group` & `js-plan` hooks kept intact! --> <div class="card ring-2 ring-indigo-600 group js-plan"> ... </div>
Centralized tailclass.css (edit once to restyle everywhere):
/* Generated by TailClass v1.0.2 — https://github.com/Infinitode/TailClass */ /* Centralized Tailwind component classes. Edit rules here to update styles site-wide. */ @layer components { /* === Navigation & Headers === */ /* Navigation Bar — 3 occurrences across 3 files (<nav>) */ .navbar { @apply flex sticky top-0 z-50 items-center justify-between px-6 py-4 bg-white/90 border-b border-slate-200 backdrop-blur; } /* Navigation Brand — 3 occurrences across 3 files (<a>) */ .navbar-brand { @apply font-bold text-slate-900 text-xl tracking-tight; } /* Desktop Navigation Menu — 2 occurrences across 2 files (<ul>) */ .nav-menu { @apply hidden gap-6 items-center md:flex; } /* Navigation Link — 6 occurrences across 2 files (<a>) */ .nav-link { @apply font-medium text-slate-600 text-sm transition hover:text-indigo-600; } /* === Cards & Surface Panels === */ /* Surface Card — 6 occurrences across 3 files (<article>, <div>) */ .card { @apply p-6 bg-white border-slate-200 border rounded-xl shadow-sm; } /* === Buttons & Interactive Controls === */ /* Primary Action Button — 6 occurrences across 4 files (<a>, <button>) */ .btn-primary { @apply inline-flex items-center justify-center px-4 py-2.5 font-semibold text-sm text-white bg-indigo-600 rounded-lg shadow-sm transition hover:bg-indigo-500; } }
- Smart Visual & Structural Naming — Combines element tags (
<nav>,<header>,<aside>,<footer>,<button>,<input>,<dialog>,<table>), ARIA roles, parent/ancestor context, JSX component names, and visual CSS signals (flex/grid geometry, sticky/fixed positioning, spacing scale, border-radius, shadows, colour semantics) to name patterns accurately. - Order-Independent Canonicalization — Shuffled utility lists across different files resolve to the same canonical layout key automatically.
- Smart Subset & Shared-Base Matching — Recognizes when an element uses a repeated component layout plus 1–2 extra contextual utilities (like
mt-8orring-2 ring-indigo-600), replacing the shared base while keeping element-specific utilities inline. Disable anytime with--no-subset. - Class List Mode & Custom Dictionaries —
tailclass listgenerates an editabletailclass.map.json(plus optional.md/.csvreports) detailing total classes, unique utilities, pattern class counts, occurrence counts, and exact file/line numbers. Fill in"customName"on any entry (or pass a simple flat JSON dictionary) to override auto-naming. - Includes, Excludes & Granular Rules — Filter files (
include/excludeglobs), keep specific utilities inline (excludeClasses: ["js-*", "animate-*"]), opt elements out (tc-ignore), filter tags (ignoreTags/onlyTags), tune thresholds (minOccurrences,minClasses,minFiles), and define custom heuristic rules intailclass.config.json. - Multi-Framework Support — Scans HTML, JSX/TSX (including
className={...}, template literals`...`without breaking${...}interpolations, andclsx/cn/twMerge/cvacalls), Vue, Svelte, Astro, Blade/PHP, ERB, EJS, Twig, Nunjucks, Liquid, and Jinja. - Safe, Incremental & Reversible — Preview diffs with
--dry-run, create backups with--backup, re-run idempotently as new pages are added, and undo everything in one command withtailclass revert. - Zero Dependencies — Pure Node.js (
>= 18), instant startup, dual CommonJS & ES Module exports, and bundled TypeScript.d.tsdefinitions.
Install globally or as a dev dependency via npm:
npm install -D tailclass
# or run directly with npx:
npx tailclass scan ./srcOr install directly from GitHub:
git clone https://github.com/Infinitode/TailClass.git
cd TailClass
npm linknpx tailclass scan ./src
npx tailclass apply ./src --dry-run
npx tailclass apply ./src -o src/styles/tailclass.css
Then import tailclass.css inside your main Tailwind stylesheet:
/* Tailwind v3 */ @tailwind base; @tailwind components; @tailwind utilities; @import "./tailclass.css";
/* Tailwind v4 */ @import "tailwindcss"; @import "./tailclass.css";
When you want complete visibility into your site's Tailwind usage — or want to customize class names before rewriting — run Class List Mode:
npx tailclass list ./src --report class-audit.md
This prints a full breakdown in your terminal and generates tailclass.map.json:
{
"$schema": "https://raw.githubusercontent.com/Infinitode/TailClass/main/schema/tailclass.map.schema.json",
"version": "1.0.2",
"summary": {
"filesScanned": 12,
"filesWithClasses": 11,
"totalClassAttributes": 148,
"totalUtilityTokens": 892,
"uniqueUtilityTokens": 114,
"uniqueClassPatterns": 46,
"extractablePatterns": 14,
"totalReplacementsPossible": 68
},
"mappings": [
{
"id": "tc_9a41c20e",
"autoName": "navbar",
"customName": "",
"enabled": true,
"category": "navigation",
"label": "Navigation Bar",
"confidence": 0.96,
"matchType": "exact",
"classCount": 12,
"classes": "flex sticky top-0 z-50 items-center justify-between px-6 py-4 bg-white/90 border-b border-slate-200 backdrop-blur",
"timesFound": 4,
"fileCount": 4,
"elements": ["nav"],
"files": [
{ "file": "index.html", "count": 1, "lines": [7] },
{ "file": "pricing.html", "count": 1, "lines": [8] }
]
}
],
"utilityInventory": [
{
"utility": "flex",
"timesFound": 42,
"fileCount": 10,
"files": ["index.html", "pricing.html", "..."]
}
]
}-
Edit
tailclass.map.json:- Set
"customName": "main-topbar"on any entry to override"autoName". - Set
"enabled": falseon any entry you want left inline. - Pass
--all(tailclass list ./src --all) to include 1-off patterns intailclass.map.json— typing a"customName"on a 1-off pattern promotes and extracts it too! - Re-running
tailclass listautomatically merges and preserves your existing"customName"and"enabled"edits.
- Set
-
Or supply a minimal hand-written flat JSON dictionary (
custom-dict.json): Keys can be utility strings (in any order), auto-detected names, or pattern IDs:{ "px-6 py-4 flex items-center justify-between bg-white": "site-topbar", "btn-primary": "brand-button", "nav-link": false } -
Apply your dictionary:
npx tailclass apply ./src --dict tailclass.map.json
Tip
If tailclass.map.json exists in your target directory, tailclass apply loads your custom overrides from it automatically! Pass --no-dict if you want to bypass tailclass.map.json and force pure automatic naming.
Scaffold a starter tailclass.config.json with:
npx tailclass init
{
"$schema": "https://raw.githubusercontent.com/Infinitode/TailClass/main/schema/tailclass.config.schema.json",
"include": [
"src/**/*.{html,jsx,tsx,vue,svelte,astro}",
"pages/**/*.{jsx,tsx}"
],
"exclude": [
"src/legacy/**",
"**/*.stories.tsx"
],
"outputCss": "src/styles/tailclass.css",
"dictionaryFile": "tailclass.map.json",
"cssFormat": "tailwind-v3",
"minOccurrences": 2,
"minClasses": 3,
"minFiles": 1,
"subsetMatching": true,
"classPrefix": "",
"namingStyle": "kebab",
"tailwindPrefix": "",
"excludeClasses": ["js-*", "qa-*"],
"includeClasses": [],
"excludePatterns": ["tc-ignore", "no-tailclass"],
"ignoreTags": ["script", "style", "svg", "path"],
"onlyTags": [],
"rules": [
{
"name": "brand-pill-cta",
"category": "controls",
"matchTag": ["a", "button"],
"mustInclude": ["rounded-full", "bg-indigo-*"],
"priority": 100
}
]
}tailclass.config.json travels with the repository, so TailClass treats it as
untrusted input:
outputCss,dictionaryFileandmanifestFilemust stay inside the project root when they come from the config file (use-o/--dicton the command line for deliberate out-of-root targets). This is a physical check: every existing path component is resolved withlstat/realpathagainst the real project root, so a directory symlink cannot redirect a write outside the project. Symlinked directories that resolve inside the root still work.applyadditionally refuses to overwrite a scanned source file, or a stylesheet/manifest that it does not own, unless--forceis used.- Behavior change in v1.0.2: an output path's final component must be a regular
file, not a symlink — even when the link points to a file inside the project
root, even with
--force, and even for an explicit CLI path. 1.0.1 wrote through such links; 1.0.2 fails withRefusing to write ... the target is a symlink ..., andrevert --cleannever deletes a symlink. Replace the link with a regular file or pointoutputCss/dictionaryFile/manifestFileat the real file. Intermediate directory symlinks are unaffected. revert --cleanonly deletes files TailClass can verify it generated; a foreign stylesheet, an unrelated JSON file, a scanned source file or a symlink is left untouched and reported as a warning.--forcedoes not override this.- Invalid values fail loudly with the offending key (
Invalid exclude[1]: expected a string, received null); unknown keys are reported as warnings on stderr. - Class tokens containing
;,{,}or comment terminators are never emitted into@apply— they simply stay inline on the element. .git/,node_modules/and.tailclass-backup/are always excluded, even withreplaceDefaultExcludes.
See SECURITY.md for the hardening guarantees and disclosure process.
Add tc-ignore (or any token listed in excludePatterns) to keep an element untouched:
<div class="tc-ignore rounded-xl border border-slate-200 bg-white p-6 shadow-sm"> Utilities on this element will never be replaced. </div>
tailclass <command> [directory] [options]
| Command | Description |
|---|---|
tailclass scan [dir] |
Scan site files and preview auto-detected semantic classes (read-only). |
tailclass list [dir] |
Class List Mode — print pattern & utility counts/locations and write tailclass.map.json. |
tailclass apply [dir] |
Generate centralized CSS (tailclass.css) and rewrite matched utility classes across files. |
tailclass revert [dir] |
Expand centralized TailClass names back into inline Tailwind utilities. |
tailclass init [dir] |
Create a starter tailclass.config.json file. |
| Flag | Description |
|---|---|
-c, --config <path> |
Explicit path to tailclass.config.json. |
-o, --output <path> |
Output path for centralized CSS (default: tailclass.css). |
--dict <path> |
Path to custom mapping dictionary JSON. |
--no-dict |
Ignore existing tailclass.map.json during apply. |
--strict-dict |
Only extract patterns explicitly present in --dict. |
-d, --dry-run |
Preview edits and generated CSS without writing to disk. |
--backup |
Back up original modified files into .tailclass-backup/. |
--force |
Overwrite a stylesheet/manifest that does not look like TailClass output on apply. Never permits --clean to delete foreign files. |
-a, --all |
Include non-repeated / 1-off patterns in list mode. |
--report <file> |
Export Markdown (.md) or CSV (.csv) audit report in list mode. |
--format <mode> |
CSS format: tailwind-v3 (default), tailwind-v4, or plain. |
--prefix <str> |
Prepend prefix to generated classes (e.g. ui- → .ui-navbar). |
--style <mode> |
Naming style: kebab (btn-primary), bem (btn--primary), or camel (btnPrimary). |
--tw-prefix <str> |
Custom Tailwind utility prefix (e.g. tw-). |
--min-occurrences <n> |
Minimum site occurrences to extract a pattern (default: 2). |
--min-classes <n> |
Minimum Tailwind utilities per pattern (default: 3). |
--min-files <n> |
Minimum distinct files a pattern must appear in (default: 1). |
--no-subset |
Disable subset / shared-base layout matching. |
-i, --include <globs> |
Comma-separated file include globs. |
-e, --exclude <globs> |
Comma-separated file exclude globs. |
--exclude-classes <globs> |
Utility globs to keep inline (e.g. "js-*,animate-*"). |
--include-classes <globs> |
Restrict extraction to matching utility globs. |
--ignore-tags <tags> |
Comma-separated HTML/JSX tags to ignore. |
--only-tags <tags> |
Restrict auto-classing to specific HTML/JSX tags. |
--clean |
After revert, remove only the stylesheet/manifest TailClass generated (foreign files are kept with a warning). |
--json |
Output structured JSON for CI pipelines and editor integrations. |
TailClass supports both CommonJS (require) and ES Modules (import), with full TypeScript declarations included.
import { scanProject, generateClassList, applyAutoClasses, revertProject, rewriteSource, canonicalizeClasses, } from "tailclass"; // 1. Scan & inspect patterns without touching disk const { analysis } = scanProject("./src", { minOccurrences: 2 }); console.log(analysis.summary); // 2. Generate Class List dictionary + Markdown report const { dictionary } = generateClassList("./src", { dictionaryFile: "tailclass.map.json", reportFile: "docs/tailwind-audit.md", }); // 3. Apply with custom overrides (or dry-run in memory) const result = applyAutoClasses("./src", { outputCss: "src/styles/tailclass.css", cssFormat: "tailwind-v4", dictionary: { navbar: "app-topbar", }, dryRun: false, }); console.log(`Saved ${result.totalBytesSaved} bytes across ${result.totalFilesModified} files.`); // 4. Pure in-memory string transformation (no filesystem needed) const canonical = canonicalizeClasses("p-6 bg-white rounded-xl border border-slate-200 shadow-sm"); const singleFile = rewriteSource( '<div class="p-6 bg-white rounded-xl border border-slate-200 shadow-sm">Hello</div>', "Card.html", [{ id: "tc_demo", finalName: "card", canonicalKey: canonical }] ); console.log(singleFile.output); // '<div class="card">Hello</div>'
TailClass combines DOM context (tag, parent/ancestors, ARIA attributes, input type, PascalCase JSX component names) with visual utility geometry to assign clean names out of the box:
| Category | Example Auto-Assigned Names | Key Visual & Structural Signals |
|---|---|---|
| Navigation | navbar, site-header, navbar-brand, nav-menu, mobile-menu, nav-link, breadcrumb, sidebar |
<nav>, <header>, <aside>, role="navigation", sticky top-0 z-50, justify-between, backdrop-blur, child <ul> / <a> |
| Page Layout | page-shell, content-container, hero-section, section-block, site-footer, divider, grid-3col, flex-between, vstack |
min-h-screen flex-col, max-w-7xl mx-auto, py-24, <footer>, <hr>, grid-cols-* |
| Controls | btn-primary, btn-outline, btn-danger, btn-soft, btn-ghost, icon-btn |
<button>, type="submit", button-padded <a>, solid brand/danger fills, borders/rings, square icon dimensions |
| Surfaces | card, card-dark, card-elevated, surface-box |
<article>, <div>, rounded-xl/2xl, border, shadow-*, surface fills & padding |
| Forms | form-layout, form-label, form-input, search-input, form-select, form-textarea, form-checkbox |
<form>, <label>, <input>, <select>, <textarea>, w-full, focus:ring-* |
| Data Display | badge, badge-success, badge-warning, badge-danger, avatar, icon-box, data-table, table-header-cell, table-cell |
rounded-full px-2.5 py-0.5 text-xs, semantic colour tints, <img> square rounded-full, <table> / <th> / <td> |
| Overlays & Feedback | modal-backdrop, modal-dialog, alert-warning, alert-danger, spinner, skeleton |
fixed inset-0 bg-black/50, <dialog>, role="dialog", role="alert", border-l-4, animate-spin, animate-pulse |
| Typography | hero-title, section-title, card-title, eyebrow, lead-text, muted-text, inline-code, code-block, blockquote, text-link |
<h1>–<h4>, text-5xl font-extrabold, uppercase tracking-wider, <code>, <pre> |
When multiple distinct patterns share an archetype, TailClass disambiguates by visual traits first (card vs card-dark vs card-elevated) before falling back to numeric suffixes.
Important
- Tailwind Build Step Required: Generated
tailclass.cssfiles use Tailwind's@applydirective. Ensuretailclass.cssis imported into your main CSS entrypoint processed by Tailwind CLI, PostCSS, or@tailwindcss/vite. - Dynamic Runtime String Concatenation: Glued runtime interpolations like
`bg-${color}-500`cannot be statically compiled into@apply; TailClass automatically leaves glued dynamic fragments untouched while still extracting any surrounding static utilities in the same template literal.
Contributions, bug reports, and new UI naming heuristics are very welcome! Please read CONTRIBUTING.md and our Code of Conduct before submitting a pull request.
Please review SECURITY.md for responsible disclosure instructions and the hardening guarantees TailClass upholds. Never report security vulnerabilities in public issues.
Released under the MIT License (Modified) — see LICENSE for full details. Substantial derivative works must be clearly distinguished from TailClass and distributed under a different name.
Developed and maintained by Infinitode Pty Ltd · Documentation · GitHub · Support on Ko-fi