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

@daviderenfeld/eslint-plugin-strict-tailwind-order

v1.10.0

Published

Strict Tailwind CSS class ordering with custom CSS detection, family-safe wrapping, and multi-page Tailwind v4 themes

Downloads

358

Readme

@daviderenfeld/eslint-plugin-strict-tailwind-order

ESLint rules for strict Tailwind CSS class ordering in Vue, JSX, TSX, and class helper calls.

The 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.

Install with pnpm

pnpm add -D @daviderenfeld/eslint-plugin-strict-tailwind-order

Existing Vue ESLint configuration

Use this preset when the project already configures Vue and TypeScript parsers:

import strictTailwindOrder from '@daviderenfeld/eslint-plugin-strict-tailwind-order';

export default defineConfigWithVueTs(
  existingConfigs,
  skipFormatting,
  strictTailwindOrder.configs['flat/vue-existing-config'],
);

This preset does not define or replace parsers, language options, globals, ignores, or existing ESLint rules.

Enabled rules

{
  'strict-tailwind-order/strict-tailwind-order': 'error',
  'strict-tailwind-order/report-unknown-classes': 'warn',
  'strict-tailwind-order/stylesheet-errors': 'warn',
}

Recommended multi-page configuration

For repositories whose files are stored under pages/01, pages/02, and so on, use the {page} placeholder:

{
  files: ['pages/**/*.{vue,js,ts,jsx,tsx}'],

  settings: {
    strictTailwindOrder: {
      stylesheet: 'pages/{page}/src/main.css',
      maxClassLineLength: 100,
    },
  },
}

A 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.

For explicit selection, use ordered glob mappings. The first match wins:

settings: {
  strictTailwindOrder: {
    stylesheets: {
      'pages/01/**': 'pages/01/src/main.css',
      'pages/02/**': 'pages/02/src/main.css',
    },
  },
},

A single-page project can continue using a normal path:

settings: {
  strictTailwindOrder: {
    stylesheet: 'pages/01/src/main.css',
  },
},

Custom CSS class detection

When a stylesheet is configured, project-defined classes are detected automatically from ordinary CSS selectors and Tailwind v4 @utility declarations.

.project-card {
  position: relative;
}

.ntlv-scrollbar-none::-webkit-scrollbar {
  display: none;
}

@utility content-auto {
  content-visibility: auto;
}

@utility tab-* {
  tab-size: --value(integer);
}

The final order is:

custom CSS classes → Tailwind utilities → unknown classes

For example:

class="mt-4 typo-class project-card bg-white flex ntlv-scrollbar-none"

becomes:

class="project-card ntlv-scrollbar-none flex bg-white mt-4 typo-class"

Detected custom classes are not reported by report-unknown-classes. Their original relative order is preserved at the beginning.

Relative local imports are followed recursively by default:

@import './components/cards.css';
@import './utilities/scrollbar.css';
@import 'tailwindcss';

Local files are scanned. Package imports and remote URLs are ignored. Import cycles are handled safely, and scanning stops at maxImportDepth, which defaults to 10.

@utility definitions support known Tailwind variants:

class="hover:content-auto tab-4"

Ordinary selector classes are matched exactly. A class such as hover:project-card remains unknown unless that exact escaped selector exists in CSS.

Disable detection or import traversal when needed:

settings: {
  strictTailwindOrder: {
    stylesheet: 'pages/{page}/src/main.css',
    detectCustomClasses: false,
    followImports: false,
    maxImportDepth: 0,
  },
},

Unknown variants and breakpoints

The whole class is treated as unknown when its variant is not recognized:

sxl:flex

It is moved to the absolute end and reported by report-unknown-classes.

The same class remains valid when sxl is declared in the selected Tailwind stylesheet:

@theme {
  --breakpoint-sxl: 110rem;
}

It can also be supplied through breakpointOrder or the rule-level breakpoints option.

Official state and arbitrary variants remain recognized, including hover:*, motion-reduce:*, group-*, peer-*, aria-*, data-*, supports-*, ui-*, and [&...]:*.

Family-safe line wrapping

Static Vue and JSX class attributes are wrapped when their class content exceeds 100 characters by default:

<div
  class="
    w-full flex flex-col items-center lg:items-start text-center lg:text-start
    motion-reduce:opacity-100 px-4
  "
/>

Wrapping rules:

  • The limit counts only the class content, not class="" or indentation.
  • A line of exactly 100 characters is not wrapped.
  • Breaks occur only between utility families.
  • Base and responsive members of one family stay together, such as items-center lg:items-start.
  • A single family may exceed the limit rather than being split.
  • Set maxClassLineLength: 0 to disable wrapping.

Ordinary 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.

Prettier order

Core 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.

Example order:

{
  "*.vue": [
    "prettier --write",
    "eslint --fix -c config/eslint.config.ts"
  ]
}

Do not enable prettier-plugin-tailwindcss at the same time, because it applies a different Tailwind ordering algorithm.

Available settings

  • stylesheet: one Tailwind v4 stylesheet path; supports {page}.
  • stylesheets: file-glob-to-stylesheet mapping; first match wins.
  • stylesheetErrors: 'report' or 'ignore'.
  • breakpointOrder: explicit responsive breakpoint order.
  • maxClassLineLength: default 100; use 0 to disable wrapping.
  • detectCustomClasses: detects CSS selector and @utility classes; default true.
  • followImports: follows relative local CSS @import chains; default true.
  • maxImportDepth: maximum local import depth; default 10.

Stylesheet behavior

  • Custom Tailwind v4 font sizes are extracted from --text-* theme variables.
  • Custom colors are extracted from --color-* variables.
  • Custom breakpoints are extracted from --breakpoint-* variables.
  • Text metadata variables such as --text-hero--line-height do not create separate size tokens.
  • --font-* variables are not treated as text sizes.
  • Mixed breakpoint units are not converted automatically.
  • Explicit breakpointOrder overrides inferred ordering.
  • Theme declarations and custom classes are read from the selected stylesheet and followed relative local imports.
  • Package imports and remote URLs are not followed.
  • Import cycles are deduplicated safely.

Stylesheet errors

Use the dedicated rule for severity:

rules: {
  'strict-tailwind-order/stylesheet-errors': 'warn',
}

Suppress stylesheet problems entirely with:

settings: {
  strictTailwindOrder: {
    stylesheetErrors: 'ignore',
  },
}

Ignore non-CSS project hooks

{
  rules: {
    'strict-tailwind-order/report-unknown-classes': [
      'warn',
      {
        ignore: ['hide-scrollbar', 'no-shadow', 'video-btn-clean'],
      },
    ],
  },
}

Use 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.

Supported syntax

  • Vue static class attributes
  • Vue Transition class attributes
  • Vue static strings inside dynamic :class
  • Vue arrays, objects, conditionals, and logical expressions in :class
  • Static strings inside cn, clsx, classNames, twJoin, and twMerge
  • JSX className
  • Responsive, state, stacked, and arbitrary variants
  • Official will-change-* utilities
  • Arbitrary CSS properties such as [will-change:transform] and [-webkit-text-stroke:...]

Dynamic template literals and concatenated strings are ignored.

Detected 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.

Run

Check without changing files:

pnpm exec eslint . -c config/eslint.config.ts --no-cache

Apply fixes:

pnpm exec eslint . --fix -c config/eslint.config.ts

Test

pnpm install
pnpm test

Publish a new version

pnpm test
pnpm pack --dry-run
pnpm publish --access public