{"_id":"@daviderenfeld/eslint-plugin-strict-tailwind-order","_rev":"2-6e3fbf84613fa251b2457d30fe5b6de6","name":"@daviderenfeld/eslint-plugin-strict-tailwind-order","dist-tags":{"latest":"1.10.0"},"versions":{"1.8.2":{"name":"@daviderenfeld/eslint-plugin-strict-tailwind-order","version":"1.8.2","keywords":["eslint","eslintplugin","tailwindcss","vue","typescript","jsx"],"license":"MIT","_id":"@daviderenfeld/eslint-plugin-strict-tailwind-order@1.8.2","maintainers":[{"name":"daviderenfeld","email":"1020dudu@gmail.com"}],"dist":{"shasum":"06deee34743d4c973cfa050a708ddaef604c375e","tarball":"https://registry.npmjs.org/@daviderenfeld/eslint-plugin-strict-tailwind-order/-/eslint-plugin-strict-tailwind-order-1.8.2.tgz","fileCount":20,"integrity":"sha512-XR2impXZD3vH98IoxbblH2w2jLbH8zHwn9GP2vGtD6B20guKvikisygGeTy4PI/WKLE436y/or1Dp7GelJu1uQ==","signatures":[{"sig":"MEYCIQCyZpfR2e0nw0Le5tw0VnE7MSyQHOk2pr9Nu66XVFPB2wIhAM/+YL7tx4WG/mZ48z19SSflxOh8XU19f2bRcBBpkE3K","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":70937},"main":"index.cjs","type":"commonjs","_from":"file:daviderenfeld-eslint-plugin-strict-tailwind-order-1.8.2.tgz","types":"index.d.ts","exports":{".":{"types":"./index.d.ts","default":"./index.cjs","require":"./index.cjs"}},"scripts":{"test":"node --test"},"_npmUser":{"name":"daviderenfeld","email":"1020dudu@gmail.com"},"_resolved":"/tmp/1f4f51ede3c442006975e2f7dfd4dce6/daviderenfeld-eslint-plugin-strict-tailwind-order-1.8.2.tgz","_integrity":"sha512-XR2impXZD3vH98IoxbblH2w2jLbH8zHwn9GP2vGtD6B20guKvikisygGeTy4PI/WKLE436y/or1Dp7GelJu1uQ==","_npmVersion":"11.6.2","description":"Strict Tailwind CSS class ordering for existing ESLint flat configurations, with Vue and dynamic class support","directories":{},"_nodeVersion":"24.12.0","publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"_hasShrinkwrap":false,"devDependencies":{"eslint":"^9.39.2","vue-eslint-parser":"^10.4.1"},"peerDependencies":{"eslint":"^9.0.0 || ^10.0.0"},"_npmOperationalInternal":{"tmp":"tmp/eslint-plugin-strict-tailwind-order_1.8.2_1782203449536_0.9154898429221388","host":"s3://npm-registry-packages-npm-production"}},"1.10.0":{"name":"@daviderenfeld/eslint-plugin-strict-tailwind-order","version":"1.10.0","description":"Strict Tailwind CSS class ordering with custom CSS detection, family-safe wrapping, and multi-page Tailwind v4 themes","main":"index.cjs","type":"commonjs","keywords":["eslint","eslintplugin","tailwindcss","vue","typescript","jsx"],"license":"MIT","peerDependencies":{"eslint":"^9.0.0 || ^10.0.0"},"devDependencies":{"eslint":"^9.39.2","vue-eslint-parser":"^10.4.1"},"types":"index.d.ts","exports":{".":{"types":"./index.d.ts","require":"./index.cjs","default":"./index.cjs"}},"publishConfig":{"access":"public","registry":"https://registry.npmjs.org/"},"scripts":{"test":"node --test"},"_id":"@daviderenfeld/eslint-plugin-strict-tailwind-order@1.10.0","_integrity":"sha512-9ud8X9sexvZ39xK6QJaM+zp1CGMXYObuUtyszIbxgi+Y28lvQe4LPdj52x4g72qAq7rTPQqfKz52f7tWSRlASA==","_resolved":"/tmp/6ce56d2fa19a84cfae712bf2daf5236c/daviderenfeld-eslint-plugin-strict-tailwind-order-1.10.0.tgz","_from":"file:daviderenfeld-eslint-plugin-strict-tailwind-order-1.10.0.tgz","_nodeVersion":"24.12.0","_npmVersion":"11.6.2","dist":{"integrity":"sha512-9ud8X9sexvZ39xK6QJaM+zp1CGMXYObuUtyszIbxgi+Y28lvQe4LPdj52x4g72qAq7rTPQqfKz52f7tWSRlASA==","shasum":"dc9103e99cc2ca29cf483d6dfd49671e170ed1c4","tarball":"https://registry.npmjs.org/@daviderenfeld/eslint-plugin-strict-tailwind-order/-/eslint-plugin-strict-tailwind-order-1.10.0.tgz","fileCount":21,"unpackedSize":96114,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHN+0N23TOPVjJCB8wt8N33sj8pV5qN3foIEbXXCYYKtAiEAnj3TYAKsocZzdxIFaAWDpu/FzY7YcJ0evz8W+RMP2K4="}]},"_npmUser":{"name":"daviderenfeld","email":"1020dudu@gmail.com"},"directories":{},"maintainers":[{"name":"daviderenfeld","email":"1020dudu@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/eslint-plugin-strict-tailwind-order_1.10.0_1782292742643_0.7261822170564969"},"_hasShrinkwrap":false}},"time":{"created":"2026-06-23T08:30:49.261Z","modified":"2026-06-24T09:19:02.879Z","1.8.2":"2026-06-23T08:30:49.705Z","1.10.0":"2026-06-24T09:19:02.769Z"},"license":"MIT","keywords":["eslint","eslintplugin","tailwindcss","vue","typescript","jsx"],"description":"Strict Tailwind CSS class ordering with custom CSS detection, family-safe wrapping, and multi-page Tailwind v4 themes","maintainers":[{"name":"daviderenfeld","email":"1020dudu@gmail.com"}],"readme":"# @daviderenfeld/eslint-plugin-strict-tailwind-order\n\nESLint rules for strict Tailwind CSS class ordering in Vue, JSX, TSX, and class helper calls.\n\nThe fixer preserves every class token exactly. It can reorder tokens and wrap long static class attributes, but it does not add, remove, rename, merge, normalize, or replace classes.\n\n## Install with pnpm\n\n```bash\npnpm add -D @daviderenfeld/eslint-plugin-strict-tailwind-order\n```\n\n## Existing Vue ESLint configuration\n\nUse this preset when the project already configures Vue and TypeScript parsers:\n\n```ts\nimport strictTailwindOrder from '@daviderenfeld/eslint-plugin-strict-tailwind-order';\n\nexport default defineConfigWithVueTs(\n  existingConfigs,\n  skipFormatting,\n  strictTailwindOrder.configs['flat/vue-existing-config'],\n);\n```\n\nThis preset does not define or replace parsers, language options, globals, ignores, or existing ESLint rules.\n\n## Enabled rules\n\n```js\n{\n  'strict-tailwind-order/strict-tailwind-order': 'error',\n  'strict-tailwind-order/report-unknown-classes': 'warn',\n  'strict-tailwind-order/stylesheet-errors': 'warn',\n}\n```\n\n## Recommended multi-page configuration\n\nFor repositories whose files are stored under `pages/01`, `pages/02`, and so on, use the `{page}` placeholder:\n\n```ts\n{\n  files: ['pages/**/*.{vue,js,ts,jsx,tsx}'],\n\n  settings: {\n    strictTailwindOrder: {\n      stylesheet: 'pages/{page}/src/main.css',\n      maxClassLineLength: 100,\n    },\n  },\n}\n```\n\nA file under `pages/02/src/...` will read `pages/02/src/main.css`. Custom colors, font sizes, and breakpoints are therefore resolved from the correct page instead of always reading page 01.\n\nFor explicit selection, use ordered glob mappings. The first match wins:\n\n```ts\nsettings: {\n  strictTailwindOrder: {\n    stylesheets: {\n      'pages/01/**': 'pages/01/src/main.css',\n      'pages/02/**': 'pages/02/src/main.css',\n    },\n  },\n},\n```\n\nA single-page project can continue using a normal path:\n\n```ts\nsettings: {\n  strictTailwindOrder: {\n    stylesheet: 'pages/01/src/main.css',\n  },\n},\n```\n\n## Custom CSS class detection\n\nWhen a stylesheet is configured, project-defined classes are detected automatically from ordinary CSS selectors and Tailwind v4 `@utility` declarations.\n\n```css\n.project-card {\n  position: relative;\n}\n\n.ntlv-scrollbar-none::-webkit-scrollbar {\n  display: none;\n}\n\n@utility content-auto {\n  content-visibility: auto;\n}\n\n@utility tab-* {\n  tab-size: --value(integer);\n}\n```\n\nThe final order is:\n\n```text\ncustom CSS classes → Tailwind utilities → unknown classes\n```\n\nFor example:\n\n```html\nclass=\"mt-4 typo-class project-card bg-white flex ntlv-scrollbar-none\"\n```\n\nbecomes:\n\n```html\nclass=\"project-card ntlv-scrollbar-none flex bg-white mt-4 typo-class\"\n```\n\nDetected custom classes are not reported by `report-unknown-classes`. Their original relative order is preserved at the beginning.\n\nRelative local imports are followed recursively by default:\n\n```css\n@import './components/cards.css';\n@import './utilities/scrollbar.css';\n@import 'tailwindcss';\n```\n\nLocal files are scanned. Package imports and remote URLs are ignored. Import cycles are handled safely, and scanning stops at `maxImportDepth`, which defaults to 10.\n\n`@utility` definitions support known Tailwind variants:\n\n```html\nclass=\"hover:content-auto tab-4\"\n```\n\nOrdinary selector classes are matched exactly. A class such as `hover:project-card` remains unknown unless that exact escaped selector exists in CSS.\n\nDisable detection or import traversal when needed:\n\n```ts\nsettings: {\n  strictTailwindOrder: {\n    stylesheet: 'pages/{page}/src/main.css',\n    detectCustomClasses: false,\n    followImports: false,\n    maxImportDepth: 0,\n  },\n},\n```\n\n## Unknown variants and breakpoints\n\nThe whole class is treated as unknown when its variant is not recognized:\n\n```text\nsxl:flex\n```\n\nIt is moved to the absolute end and reported by `report-unknown-classes`.\n\nThe same class remains valid when `sxl` is declared in the selected Tailwind stylesheet:\n\n```css\n@theme {\n  --breakpoint-sxl: 110rem;\n}\n```\n\nIt can also be supplied through `breakpointOrder` or the rule-level `breakpoints` option.\n\nOfficial state and arbitrary variants remain recognized, including `hover:*`, `motion-reduce:*`, `group-*`, `peer-*`, `aria-*`, `data-*`, `supports-*`, `ui-*`, and `[&...]:*`.\n\n## Family-safe line wrapping\n\nStatic Vue and JSX class attributes are wrapped when their class content exceeds 100 characters by default:\n\n```vue\n<div\n  class=\"\n    w-full flex flex-col items-center lg:items-start text-center lg:text-start\n    motion-reduce:opacity-100 px-4\n  \"\n/>\n```\n\nWrapping rules:\n\n- The limit counts only the class content, not `class=\"\"` or indentation.\n- A line of exactly 100 characters is not wrapped.\n- Breaks occur only between utility families.\n- Base and responsive members of one family stay together, such as `items-center lg:items-start`.\n- A single family may exceed the limit rather than being split.\n- Set `maxClassLineLength: 0` to disable wrapping.\n\nOrdinary quoted JavaScript strings are sorted but are not line-wrapped, because inserting a raw newline would make them invalid JavaScript. Static template literals may be wrapped safely.\n\n## Prettier order\n\nCore Prettier collapses whitespace inside a static `class` attribute. To preserve this plugin's family-safe wrapping, run ESLint after Prettier in formatting or pre-commit workflows.\n\nExample order:\n\n```json\n{\n  \"*.vue\": [\n    \"prettier --write\",\n    \"eslint --fix -c config/eslint.config.ts\"\n  ]\n}\n```\n\nDo not enable `prettier-plugin-tailwindcss` at the same time, because it applies a different Tailwind ordering algorithm.\n\n## Available settings\n\n- `stylesheet`: one Tailwind v4 stylesheet path; supports `{page}`.\n- `stylesheets`: file-glob-to-stylesheet mapping; first match wins.\n- `stylesheetErrors`: `'report'` or `'ignore'`.\n- `breakpointOrder`: explicit responsive breakpoint order.\n- `maxClassLineLength`: default `100`; use `0` to disable wrapping.\n- `detectCustomClasses`: detects CSS selector and `@utility` classes; default `true`.\n- `followImports`: follows relative local CSS `@import` chains; default `true`.\n- `maxImportDepth`: maximum local import depth; default `10`.\n\n## Stylesheet behavior\n\n- Custom Tailwind v4 font sizes are extracted from `--text-*` theme variables.\n- Custom colors are extracted from `--color-*` variables.\n- Custom breakpoints are extracted from `--breakpoint-*` variables.\n- Text metadata variables such as `--text-hero--line-height` do not create separate size tokens.\n- `--font-*` variables are not treated as text sizes.\n- Mixed breakpoint units are not converted automatically.\n- Explicit `breakpointOrder` overrides inferred ordering.\n- Theme declarations and custom classes are read from the selected stylesheet and followed relative local imports.\n- Package imports and remote URLs are not followed.\n- Import cycles are deduplicated safely.\n\n## Stylesheet errors\n\nUse the dedicated rule for severity:\n\n```ts\nrules: {\n  'strict-tailwind-order/stylesheet-errors': 'warn',\n}\n```\n\nSuppress stylesheet problems entirely with:\n\n```ts\nsettings: {\n  strictTailwindOrder: {\n    stylesheetErrors: 'ignore',\n  },\n}\n```\n\n## Ignore non-CSS project hooks\n\n```js\n{\n  rules: {\n    'strict-tailwind-order/report-unknown-classes': [\n      'warn',\n      {\n        ignore: ['hide-scrollbar', 'no-shadow', 'video-btn-clean'],\n      },\n    ],\n  },\n}\n```\n\nUse `ignore` only for tokens that are intentionally used as JavaScript, analytics, or external-library hooks but are not defined in the project CSS. Ignored classes are omitted from warnings and remain at the end.\n\n## Supported syntax\n\n- Vue static `class` attributes\n- Vue Transition class attributes\n- Vue static strings inside dynamic `:class`\n- Vue arrays, objects, conditionals, and logical expressions in `:class`\n- Static strings inside `cn`, `clsx`, `classNames`, `twJoin`, and `twMerge`\n- JSX `className`\n- Responsive, state, stacked, and arbitrary variants\n- Official `will-change-*` utilities\n- Arbitrary CSS properties such as `[will-change:transform]` and `[-webkit-text-stroke:...]`\n\nDynamic template literals and concatenated strings are ignored.\n\nDetected custom CSS classes move to the beginning, Tailwind utilities follow, and unknown classes or unknown variants move to the end. Relative order is preserved inside the custom and unknown groups.\n\n## Run\n\nCheck without changing files:\n\n```bash\npnpm exec eslint . -c config/eslint.config.ts --no-cache\n```\n\nApply fixes:\n\n```bash\npnpm exec eslint . --fix -c config/eslint.config.ts\n```\n\n## Test\n\n```bash\npnpm install\npnpm test\n```\n\n## Publish a new version\n\n```bash\npnpm test\npnpm pack --dry-run\npnpm publish --access public\n```\n","readmeFilename":"README.md"}