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.