tailwind2-7 Exercise 2: Prettier and ESLint Agreeing With Each Other
=====================================================================
Setup: npm install tailwindcss prettier prettier-plugin-tailwindcss eslint eslint-plugin-tailwindcss eslint-plugin-better-tailwindcss @typescript-eslint/parser (versions: see the chapter)
--- app.css ---
@import "tailwindcss";
@theme { --color-brand: oklch(55% .2 260); }
--- .prettierrc ---
{ "plugins": ["prettier-plugin-tailwindcss"], "tailwindStylesheet": "./app.css", "tailwindFunctions": ["cn", "cva"] }
--- page.html (input) ---
--- comp.jsx (input) ---
import { cva } from 'class-variance-authority';
const cn = (...a) => a.join(' ');
export const A = () =>
a
;
export const B = () =>
b
;
export const v = cva("p-4 flex mt-2", { variants: { s: { a: "text-lg px-2 flex" } } });
npx prettier --write page.html comp.jsx
page.html now: class="custom-thing flex rounded-md bg-brand p-2 px-4 font-bold text-white hover:bg-brand md:px-8 dark:bg-black"
comp.jsx now: cn("mt-2 flex items-center p-4", x && "bg-red-500 p-1 text-white") and cva("mt-2 flex p-4", { variants: { s: { a: "flex px-2 text-lg" } } })
Without tailwindStylesheet the same page.html became hover:bg-brand bg-brand custom-thing flex rounded-md p-2 px-4 ... (bg-brand, a theme name, was treated as unknown and moved to the front).
Without tailwindFunctions the strings in className={cn(...)} were still sorted but the cva call stayed "p-4 flex mt-2" and "text-lg px-2 flex".
--- eslint.config.js (eslint-plugin-tailwindcss 4.4.0) ---
import tw from 'eslint-plugin-tailwindcss';
import parser from '@typescript-eslint/parser';
export default [
tw.configs.recommended,
{ files: ['**/*.jsx'], languageOptions: { parser, parserOptions: { ecmaFeatures: { jsx: true } } },
settings: { tailwindcss: { cssConfigPath: './app.css' } } },
];
--- eslint2.config.js (eslint-plugin-better-tailwindcss 4.9.0), run with npx eslint -c eslint2.config.js file.jsx ---
import btw from 'eslint-plugin-better-tailwindcss';
import parser from '@typescript-eslint/parser';
export default [
{ files: ['**/*.jsx'], languageOptions: { parser, parserOptions: { ecmaFeatures: { jsx: true } } },
plugins: { 'better-tailwindcss': btw },
settings: { 'better-tailwindcss': { entryPoint: 'app.css' } },
rules: { ...btw.configs.recommended.rules } },
];
--- t.jsx (the bad file) ---
export const A = () =>
a
;
eslint-plugin-tailwindcss:
'p-4' conflicts with 'p-2' (error) ; 'flex' conflicts with 'block' (error) ; 'bg-brandd' is not a Tailwind CSS class (warning) ; 'unknown-thing' is not a Tailwind CSS class (warning) ; Invalid classnames order (warnings)
better-tailwindcss:
Conflicting class detected: "p-4" and "p-2" (padding) ; "flex" and "block" (display) ; Unknown class detected: bg-brandd ; Unknown class detected: unknown-thing ; Incorrect class order (warning) ; Incorrect line wrapping (warning)
After npx prettier --write ok.jsx on a clean line with a theme colour (class "mt-2 flex items-center bg-brand p-4 text-white hover:bg-brand md:px-8 dark:bg-black"):
eslint-plugin-tailwindcss: no output (agrees)
better-tailwindcss: no order complaint; ONE warning, "Incorrect line wrapping" (wants the classes split over four lines, which Prettier does not do)
WHY THIS WORKS AS AN ANSWER
---------------------------
Both linters found the same three kinds of problem (a conflict, a typo, an unknown class) because they read app.css, so bg-brand was accepted and bg-brandd was not; Prettier fixed ordering but cannot fix a conflict or a typo, which is why you
want both. The disagreement is the second result: Prettier's order satisfies both linters' order rules, but better-tailwindcss's line-wrapping rule conflicts with Prettier's formatting. Turn that rule off
("better-tailwindcss/enforce-consistent-line-wrapping": "off"; I did not run this exact setting) so each job has one owner. Two traps I hit: eslint-plugin-tailwindcss 4.4.0 only exposes configs.recommended (the
README's "flat/recommended" name does not exist in that version: print Object.keys(plugin.configs)), and Prettier without tailwindStylesheet sorts theme-named classes as unknown.