| |

Tailwind CSS 73 🎨 Custom ESLint and Stylelint Rules for Enforcing Tailwind Utility Ordering

⏱️ Estimated read: 12 min | |

A Tailwind class list is a small program. The order of the classes does not affect the CSS cascade in most cases, because the utilities are generated in a fixed order and the specificity is the same. The order does affect the readability, the diff, the review, and the merge conflict. Two developers who add a class in different positions produce a conflict that has nothing to do with the logic. The prettier-plugin-tailwindcss is the tool that sorts the classes, and the ESLint and the Stylelint rules are the enforcement that keeps the order consistent across the team and the CI.

Key point: The prettier-plugin-tailwindcss sorts the classes in the markup. The ESLint plugin catches the deprecated and the invalid utilities, the conflicting classes, and the discouraged patterns. The Stylelint plugin catches the issues in the @apply and the custom CSS. The three tools are the different jobs, and they compose into the one pipeline.


Why the ordering rules exist

The consistency problem. The team has the different conventions: the “flex items-center” one, the “items-center flex” one, and the alphabetical one. The diff shows the reorder, and the review is the noise. The sorted order is the one convention.

The conflict problem. The two branches add the classes to the same element, and the merge conflict is the whole line. The sorted order does not remove the conflict, but it makes the resolution mechanical: the union of the two sets, sorted.

The review problem. The reviewer reads the class list to understand the element. The unsorted list hides the important class in the middle of the noise. The sorted list groups the related utilities, and the scan is the fast.

The deprecated problem. The v3 to the v4 renamed the utilities: the shadow-sm to the shadow-xs, the rounded to the rounded-sm, the outline-none to the outline-hidden. The ESLint plugin catches the deprecated names and suggests the replacement.

The conflict-class problem. The bg-red-500 bg-blue-500 is the conflict: the two classes set the same property, and the second wins. The ESLint plugin detects the conflict and warns.

The dynamic-class problem. The template literal produces the class the engine cannot see. The ESLint plugin has the rule that flags the dynamic construction, and the fix is the static form.

The plugin order problem. The prettier-plugin-tailwindcss must be the last plugin in the Prettier config. The plugin uses the Tailwind config to determine the sort order, and the wrong position produces the wrong order.


a. The Prettier plugin

The prettier-plugin-tailwindcss is the official plugin. It sorts the classes in the class and the className attributes, and it does not reorder the CSS or the @apply.

The installation

npm install -D prettier prettier-plugin-tailwindcss

The plugin is the dev dependency, and the prettier is the peer.

The configuration

// .prettierrc
{
  "plugins": ["prettier-plugin-tailwindcss"]
}

The plugin is the last in the list. The order matters because the plugin needs the final AST.

The v4 configuration

// .prettierrc
{
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindStylesheet": "./src/app.css"
}

The tailwindStylesheet points to the CSS file with the @import "tailwindcss". The plugin reads the @theme and the @utility to determine the sort order.

The sort order

The plugin sorts by the utility category, then by the value.

// Before the sort
<div className="p-4 bg-blue-600 flex items-center text-white rounded-md">

// After the sort
<div className="flex items-center rounded-md bg-blue-600 p-4 text-white">

The order is the layout first, the box second, the background, the text, and the rest.

The sorted categories

The orderThe category
1The layout (the display, the position)
2The flexbox and the grid
3The spacing (the margin, the padding)
4The sizing (the width, the height)
5The typography (the font, the text)
6The background
7The border
8The effect (the shadow, the opacity)
9The state (the hover, the focus)

The exact order is defined by the plugin and the Tailwind’s internal order.

The function and the attributes

The plugin sorts the class and the className, the tw, the clsx, the cn, the cva, and the classnames.

// The cn
<div className={cn('p-4 flex', isActive && 'bg-blue-600')}>

// The cva
const button = cva('rounded px-4 py-2', { variants: { ... } });

The plugin recognizes the patterns and sorts the strings.

The ignore

The prettier-ignore comment skips the node.

{/* prettier-ignore */}
<div className="p-4 bg-blue-600 flex">

The plugin leaves the node unchanged.

The plugin table

ElementPurpose
prettier-plugin-tailwindcssThe sort
tailwindStylesheetThe v4 config path
tailwindConfigThe v3 config path
The prettier-ignoreThe skip
The cn, the clsx, the cvaThe recognized

b. The ESLint plugin

The eslint-plugin-tailwindcss is the community plugin. It catches the issues the Prettier does not: the deprecated, the invalid, the conflicting, and the dynamic.

The installation

npm install -D eslint eslint-plugin-tailwindcss

The flat config

// eslint.config.js
import tailwind from 'eslint-plugin-tailwindcss';

export default [
  ...tailwind.configs['flat/recommended'],
];

The flat config spreads the recommended set.

The legacy config

// .eslintrc
{
  "extends": ["plugin:tailwindcss/recommended"]
}

The legacy config extends the recommended.

The rules

The ruleThe purpose
no-custom-classnameThe warn on the unknown class
no-contradicting-classnameThe warn on the conflict
enforces-shorthandThe suggest the shorthand
no-unnecessary-arbitrary-valueThe warn on the redundant
classnames-orderThe warn on the order
no-arbitrary-valueThe warn on the arbitrary
no-dynamic-class-namesThe warn on the template

The no-contradicting-classname

// ❌ The conflict
<div className="bg-red-500 bg-blue-500">

// ✅ The conditional
<div className={isActive ? 'bg-blue-500' : 'bg-red-500'}>

The rule detects the two classes that set the same property.

The no-custom-classname

// ❌ The unknown class
<div className="bg-blue-600 my-custom-class">

The rule checks the class against the Tailwind’s known set.

The no-dynamic-class-names

// ❌ The dynamic
<div className={`text-${color}-600`}>

// ✅ The static
<div className={color === 'red' ? 'text-red-600' : 'text-blue-600'}>

The rule flags the template literal, and the fix is the static or the CSS variable.

The settings

export default [
  ...tailwind.configs['flat/recommended'],
  {
    settings: {
      tailwindcss: {
        config: 'tailwind.config.js',
        cssConfigPath: 'src/app.css',
        whitelist: ['my-custom-class'],
      },
    },
  },
];

The config points to the v3 config, the cssConfigPath to the v4 CSS, and the whitelist lists the custom classes.

The ESLint table

The settingThe purpose
configThe v3 config
cssConfigPathThe v4 CSS
whitelistThe custom classes
calleesThe function names
tagsThe attribute names

c. The Stylelint plugin

The stylelint-config-tailwindcss and the stylelint-plugin-tailwindcss are the CSS-side. They check the @apply and the custom CSS.

The installation

npm install -D stylelint stylelint-config-tailwindcss

The configuration

// .stylelintrc
{
  "extends": ["stylelint-config-standard", "stylelint-config-tailwindcss"]
}

The config extends the standard and the Tailwind.

The at-rule checks

The plugin checks the @apply, the @theme, the @utility, and the @custom-variant.

/* ❌ The unknown utility in the @apply */
.button {
  @apply bg-blue-600 my-unknown-utility;
}

/* ✅ The known */
.button {
  @apply bg-blue-600 px-4 py-2;
}

The custom utility checks

@utility tab-4 {
  tab-size: 4;
}

The plugin validates the custom utility syntax.

The rule list

The ruleThe purpose
tailwindcss/no-unknown-applyThe unknown utility in the @apply
tailwindcss/no-deprecated-applyThe deprecated utility
tailwindcss/no-unknown-themeThe unknown @theme
tailwindcss/no-unknown-utilityThe unknown @utility

The CSS order

The plugin also checks the order in the custom CSS, and it can enforce the @layer order.

The Stylelint table

The configThe purpose
stylelint-config-standardThe standard rules
stylelint-config-tailwindcssThe Tailwind rules
The @apply checkThe unknown utility
The @theme checkThe unknown token

Complete Example Session

// ============================================
// PART 1: THE PRETTIER CONFIG
// ============================================

// .prettierrc
{
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindStylesheet": "./src/app.css"
}

// ============================================
// PART 2: THE PRETTIER SORT
// ============================================

// Before
<div className="p-4 bg-blue-600 flex items-center text-white rounded-md">

// After
<div className="flex items-center rounded-md bg-blue-600 p-4 text-white">

// ============================================
// PART 3: THE ESLINT FLAT CONFIG
// ============================================

// eslint.config.js
import tailwind from 'eslint-plugin-tailwindcss';

export default [
  ...tailwind.configs['flat/recommended'],
  {
    settings: {
      tailwindcss: {
        cssConfigPath: 'src/app.css',
      },
    },
  },
];

// ============================================
// PART 4: THE CONTRASTING CLASS
// ============================================

// ❌ The conflict
<div className="bg-red-500 bg-blue-500">

// ✅ The conditional
<div className={isActive ? 'bg-blue-500' : 'bg-red-500'}>

// ============================================
// PART 5: THE UNKNOWN CLASS
// ============================================

// ❌ The unknown
<div className="bg-blue-600 my-custom-class">

// ✅ The whitelist
{
  settings: {
    tailwindcss: {
      whitelist: ['my-custom-class'],
    },
  },
}

// ============================================
// PART 6: THE DYNAMIC CLASS
// ============================================

// ❌ The dynamic
<div className={`text-${color}-600`}>

// ✅ The static
<div className={color === 'red' ? 'text-red-600' : 'text-blue-600'}>

// ============================================
// PART 7: THE STYLELINT CONFIG
// ============================================

// .stylelintrc
{
  "extends": ["stylelint-config-standard", "stylelint-config-tailwindcss"]
}

// ============================================
// PART 8: THE APPLY CHECK
// ============================================

// ❌ The unknown utility in the @apply
.button {
  @apply bg-blue-600 my-unknown-utility;
}

// ✅ The known
.button {
  @apply bg-blue-600 px-4 py-2;
}

// ============================================
// PART 9: THE CVA AND THE CN
// ============================================

// The cn
<div className={cn('p-4 flex', isActive && 'bg-blue-600')}>

// The cva
const button = cva('rounded px-4 py-2', {
  variants: {
    intent: {
      primary: 'bg-blue-600 text-white',
      danger: 'bg-red-600 text-white',
    },
  },
});

// ============================================
// PART 10: THE COMPLETE PATTERN
// ============================================

// 1. Install the prettier-plugin-tailwindcss.
// 2. Set the tailwindStylesheet for the v4.
// 3. Install the eslint-plugin-tailwindcss.
// 4. Use the flat/recommended config.
// 5. Install the stylelint-config-tailwindcss.
// 6. Extend the standard and the Tailwind.
// 7. Use the whitelist for the custom classes.
// 8. Use the prettier-ignore for the exceptions.
// 9. Run the three in the CI.
// 10. The consistent order across the team.

The ten parts showed the Prettier config, the Prettier sort, the ESLint flat config, the contrasting class, the unknown class, the dynamic class, the Stylelint config, the @apply check, the cva and the cn, and the complete pattern.


Quick Reference

The Prettier Plugin

ElementPurpose
prettier-plugin-tailwindcssThe sort
tailwindStylesheetThe v4 CSS
tailwindConfigThe v3 config
The prettier-ignoreThe skip
The cn, the clsx, the cvaThe recognized

The ESLint Rules

The ruleThe purpose
no-custom-classnameThe unknown class
no-contradicting-classnameThe conflict
enforces-shorthandThe shorthand
classnames-orderThe order
no-dynamic-class-namesThe dynamic
no-arbitrary-valueThe arbitrary

The ESLint Settings

The settingThe purpose
configThe v3 config
cssConfigPathThe v4 CSS
whitelistThe custom classes
calleesThe function names
tagsThe attribute names

The Stylelint Rules

The ruleThe purpose
no-unknown-applyThe unknown @apply
no-deprecated-applyThe deprecated
no-unknown-themeThe unknown @theme
no-unknown-utilityThe unknown @utility

The Sort Categories

The orderThe category
1The layout
2The flexbox and the grid
3The spacing
4The sizing
5The typography
6The background
7The border
8The effect
9The state

Best Practices

✅ Do This:

// Use the prettier-plugin-tailwindcss.
{
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindStylesheet": "./src/app.css"
}  // ✅
// Use the eslint flat/recommended.
import tailwind from 'eslint-plugin-tailwindcss';
export default [...tailwind.configs['flat/recommended']];  // ✅
// Use the whitelist for the custom classes.
settings: { tailwindcss: { whitelist: ['my-custom-class'] } }  // ✅
// Use the static class.
className={color === 'red' ? 'text-red-600' : 'text-blue-600'}  // ✅
// Use the cn and the cva.
className={cn('p-4 flex', isActive && 'bg-blue-600')}  // ✅
// Use the stylelint config.
{
  "extends": ["stylelint-config-standard", "stylelint-config-tailwindcss"]
}  // ✅

❌ Don’t Do This:

// Don't put the plugin first.
{
  "plugins": ["prettier-plugin-tailwindcss", "other-plugin"]
}  // ❌ The plugin must be last
// Don't use the dynamic class.
className={`text-${color}-600`}  // ❌ The engine cannot see
// Don't use the conflicting classes.
className="bg-red-500 bg-blue-500"  // ❌ The conflict
/* Don't use the unknown utility in the @apply. */
.button { @apply my-unknown-utility; }  // ❌
// Don't skip the prettier-ignore without the reason.
{/* prettier-ignore */}
<div className="...">  // ⚠️ Document the reason
// Don't forget the CSS config path.
settings: { tailwindcss: { cssConfigPath: 'src/app.css' } }  // ✅

Common Pitfalls

PitfallWhy It HappensFix
The sort not workingThe plugin not lastMove it last
The v4 config not readNo tailwindStylesheetAdd the path
The custom class flaggedNot in the whitelistAdd it
The dynamic class flaggedThe template literalUse the static
The conflict not caughtNo ESLintAdd the plugin
The @apply not checkedNo StylelintAdd the config
The conflicting mergeThe unsorted classesUse the sort

Real-World Examples

1. The Prettier Config

{
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindStylesheet": "./src/app.css"
}

2. The Sorted Classes

<div className="flex items-center rounded-md bg-blue-600 p-4 text-white">

3. The ESLint Flat Config

import tailwind from 'eslint-plugin-tailwindcss';
export default [...tailwind.configs['flat/recommended']];

4. The Whitelist

settings: { tailwindcss: { whitelist: ['my-custom-class'] } }

5. The Static Class

className={color === 'red' ? 'text-red-600' : 'text-blue-600'}

6. The cn

className={cn('p-4 flex', isActive && 'bg-blue-600')}

7. The cva

const button = cva('rounded px-4 py-2', { variants: { ... } });

8. The Stylelint Config

{
  "extends": ["stylelint-config-standard", "stylelint-config-tailwindcss"]
}

9. The @apply

.button { @apply bg-blue-600 px-4 py-2; }

10. The Complete Pattern

{
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindStylesheet": "./src/app.css"
}

Visual

The Tool Chain

┌────────────────────────────────────────────────────────────┐
│  THE TOOL CHAIN                                            │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE PRETTIER                                      │     │
│  │    The prettier-plugin-tailwindcss.                │     │
│  │    The sort the classes.                           │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE ESLINT                                        │     │
│  │    The eslint-plugin-tailwindcss.                  │     │
│  │    The deprecated, the conflict, the dynamic.      │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE STYLELINT                                     │     │
│  │    The stylelint-config-tailwindcss.               │     │
│  │    The @apply, the @theme, the @utility.           │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE CI                                            │     │
│  │    The three run, the build fails on the error.    │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

The Prettier Sort

┌────────────────────────────────────────────────────────────┐
│  THE PRETTIER SORT                                         │
│                                                            │
│  THE BEFORE                                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  className="p-4 bg-blue-600 flex items-center      │     │
│  │             text-white rounded-md"                 │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  THE AFTER                                                 │
│  ┌──────────────────────────────────────────────────┐     │
│  │  className="flex items-center rounded-md           │     │
│  │             bg-blue-600 p-4 text-white"            │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  The order: the layout, the box, the background, the text. │
└────────────────────────────────────────────────────────────┘

The ESLint Rules

┌────────────────────────────────────────────────────────────┐
│  THE ESLINT RULES                                          │
│                                                            │
│  THE CONFLICT                                              │
│  ┌──────────────────────────────────────────────────┐     │
│  │  className="bg-red-500 bg-blue-500"                │     │
│  │    → the warn: the contradicting classname         │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  THE DYNAMIC                                               │
│  ┌──────────────────────────────────────────────────┐     │
│  │  className={`text-${color}-600`}                   │     │
│  │    → the warn: the dynamic class name              │     │
│  └──────────────────────────────────────────────────┘     │
│                                                            │
│  THE UNKNOWN                                               │
│  ┌──────────────────────────────────────────────────┐     │
│  │  className="bg-blue-600 my-custom"                 │     │
│  │    → the warn: the custom classname                │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

The CI Pipeline

┌────────────────────────────────────────────────────────────┐
│  THE CI PIPELINE                                           │
│                                                            │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE PRETTIER CHECK                                │     │
│  │    npx prettier --check .                          │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE ESLINT CHECK                                  │     │
│  │    npx eslint .                                    │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE STYLELINT CHECK                               │     │
│  │    npx stylelint "**/*.css"                        │     │
│  └────────────────────────┬─────────────────────────┘     │
│                           │                                │
│                           v                                │
│  ┌──────────────────────────────────────────────────┐     │
│  │  THE BUILD                                         │     │
│  └──────────────────────────────────────────────────┘     │
└────────────────────────────────────────────────────────────┘

Summary

ItemValue
prettier-plugin-tailwindcssThe sort the classes
tailwindStylesheetThe v4 CSS path
eslint-plugin-tailwindcssThe ESLint rules
stylelint-config-tailwindcssThe Stylelint rules
The no-contradicting-classnameThe conflict detection
The no-dynamic-class-namesThe dynamic detection
The whitelistThe custom classes
The prettier-ignoreThe skip
The sort categoriesThe utility order
The CI checkThe enforcement

Key takeaways:

  • The prettier-plugin-tailwindcss sorts the classes. The plugin sorts by the utility category, and the order is the Tailwind’s internal. The plugin must be the last in the Prettier config.
  • The tailwindStylesheet points to the v4 CSS. The plugin reads the @theme and the @utility to determine the sort order. The v3 uses the tailwindConfig.
  • The eslint-plugin-tailwindcss catches the issues the Prettier does not. The no-contradicting-classname detects the conflict, the no-dynamic-class-names detects the template literal, and the no-custom-classname detects the unknown.
  • The stylelint-config-tailwindcss checks the CSS. The @apply, the @theme, and the @utility are the checked. The unknown utility in the @apply is the error.
  • The whitelist lists the custom classes. The plugin would flag the custom class as the unknown, and the whitelist is the exception. The list is the configuration.
  • The prettier-ignore skips the node. The comment is the exception, and the plugin leaves the node unchanged. The reason should be the documented.
  • The three tools compose into the CI pipeline. The Prettier check, the ESLint check, and the Stylelint check run in the CI, and the build fails on the error.
  • The consistent order reduces the merge conflict. The sorted order does not remove the conflict, but the resolution is the mechanical: the union of the two sets, sorted.

Remember: The prettier-plugin-tailwindcss, the eslint-plugin-tailwindcss, and the stylelint-config-tailwindcss are the enforcement. The Prettier sorts, the ESLint catches the issues, and the Stylelint checks the CSS. The result is the consistent order across the team, the clean diff, and the CI that catches the regressions.


Was this helpful?


Stop using slow, ad-bloated tool sites! 🤮

🔎 Search “KandZ Tools” on Google to use many professional utilities for free.

KandZ.me is the ultimate minimalist hub for:
✅ Finance (Mortgage, Interest, Inflation)
✅ Tech (Base64, JSON, Dev Suite, IP)
✅ Health (BMI, BMR, TDEE)
✅ Productivity (Timer, Workspace, QR)

⚡️ Fast & Private
🔒 No data leaves your device
💎 100% Free

🔗 Use it now: https://tools.kandz.me
🔖 Bookmark it—you’ll need it later!