ESLint Plugins
Install and configure ESLint plugins for React, TypeScript, accessibility, Tailwind CSS, and testing.
Recipe
Quick-reference recipe card — copy-paste ready.
# Core plugins (most included with next/core-web-vitals)
npm install --save-dev eslint-plugin-react eslint-plugin-react-hooks
# TypeScript
npm install --save-dev @typescript-eslint/eslint-plugin @typescript-eslint/parser
# Imports
npm install --save-dev eslint-plugin-import
# Accessibility
npm install --save-dev eslint-plugin-jsx-a11y
# Tailwind CSS
npm install --save-dev eslint-plugin-tailwindcss
# Testing Library
npm install --save-dev eslint-plugin-testing-libraryWhen to reach for this: When you need rules beyond what next/core-web-vitals provides — Tailwind class ordering, test best practices, or stricter accessibility checks.
Working Example
// eslint.config.mjs
import { FlatCompat } from "@eslint/eslintrc";
import tailwindcss from "eslint-plugin-tailwindcss";
import { dirname } from "path";
import { fileURLToPath } from "url";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({ baseDirectory: __dirname });
const eslintConfig = [
// Next.js presets (includes react, react-hooks, import, jsx-a11y)
...compat.extends("next/core-web-vitals", "next/typescript"),
// Tailwind CSS plugin (flat config native)
...tailwindcss.configs["flat/recommended"],
// Testing Library (only for test files)
{
files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
...compat.extends("plugin:testing-library/react")[0],
},
// Custom rule overrides
{
rules: {
// Tailwind
"tailwindcss/classnames-order": "warn",
"tailwindcss/no-custom-classname": "off",
// Accessibility
"jsx-a11y/anchor-is-valid": "warn",
"jsx-a11y/alt-text": "error",
// TypeScript
"@typescript-eslint/no-unused-vars": [
"error",
{ argsIgnorePattern: "^_" },
],
"@typescript-eslint/consistent-type-imports": "error",
// Imports
"import/order": [
"warn",
{
groups: [
"builtin",
"external",
"internal",
["parent", "sibling"],
"index",
"type",
],
"newlines-between": "always",
alphabetize: { order: "asc" },
},
],
},
},
];
export default eslintConfig;What this demonstrates:
- Layering plugins on top of Next.js presets
- Tailwind plugin using its native flat config support
- Scoping the testing-library plugin to only test files
- Import ordering with group separation
Deep Dive
How It Works
- Plugins provide collections of rules under a namespace (e.g.,
react/,jsx-a11y/) next/core-web-vitalsalready includeseslint-plugin-react,eslint-plugin-react-hooks,eslint-plugin-next,eslint-plugin-import, andeslint-plugin-jsx-a11y- Additional plugins like
tailwindcssandtesting-libraryare added on top - In flat config, some plugins export
configs["flat/recommended"]directly; others needFlatCompat
Variations
Plugin overview:
| Plugin | Namespace | What It Does |
|---|---|---|
eslint-plugin-react | react/ | JSX best practices, component patterns |
eslint-plugin-react-hooks | react-hooks/ | Hook rules and dependency checking |
@typescript-eslint/eslint-plugin | @typescript-eslint/ | TypeScript-specific rules |
eslint-plugin-import | import/ | Import ordering, no duplicates, no unresolved |
eslint-plugin-jsx-a11y | jsx-a11y/ | Accessibility rules for JSX elements |
eslint-plugin-tailwindcss | tailwindcss/ | Class ordering, no contradicting classes |
eslint-plugin-testing-library | testing-library/ | Best practices for Testing Library |
Tailwind plugin key rules:
{
rules: {
"tailwindcss/classnames-order": "warn", // Sort classes
"tailwindcss/no-custom-classname": "off", // Allow custom classes
"tailwindcss/no-contradicting-classname": "error", // Catch p-4 p-8
"tailwindcss/enforces-negative-arbitrary-values": "warn",
},
}Testing Library plugin (scoped to test files):
{
files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
rules: {
"testing-library/await-async-queries": "error",
"testing-library/no-debugging-utils": "warn",
"testing-library/no-dom-import": "error",
"testing-library/prefer-screen-queries": "warn",
},
}TypeScript Notes
// @typescript-eslint key rules:
// no-unused-vars — catches unused imports and variables
// no-explicit-any — flags `any` type usage
// consistent-type-imports — enforces `import type { X }` syntax
// no-non-null-assertion — flags `obj!.prop` assertions
// prefer-nullish-coalescing — flags `||` where `??` is safer
// These complement tsconfig strict mode but catch
// different things (patterns vs types)Gotchas
Things that will bite you. Each gotcha includes what goes wrong, why it happens, and the fix.
-
Duplicate plugin registration — Installing
eslint-plugin-reactmanually whennext/core-web-vitalsalready includes it causes conflicts. Fix: Check what the preset includes before adding plugins. Only add plugins not already bundled. -
Tailwind plugin requires config —
eslint-plugin-tailwindcssneeds to find yourtailwind.config.*file. If your config is in a non-standard location, the plugin fails silently. Fix: Set theconfigoption in the plugin settings. -
Testing plugin on all files — Applying
testing-libraryrules globally flags non-test code. Fix: Always scope withfiles: ["**/*.test.*"]. -
Flat config compatibility — Not all plugins support flat config natively. Some need
FlatCompatwrapping, which can cause unexpected behavior. Fix: Check the plugin's README for flat config support and useFlatCompatonly when needed. -
Performance with many plugins — Each plugin adds parsing and rule-checking time. Five or more plugins can noticeably slow down linting. Fix: Use
TIMING=1 npx eslint .to profile which rules are slow and disable any you do not need.
Alternatives
Other ways to solve the same problem — and when each is the better choice.
| Alternative | Use When | Don't Use When |
|---|---|---|
| Biome | You want linting + formatting in one fast tool | You need Tailwind or testing-library rules |
next/core-web-vitals only | The bundled plugins are sufficient for your project | You need Tailwind class ordering or test rules |
oxlint | You want blazing-fast linting with common rules | You need plugin-specific rules (Tailwind, testing) |
FAQs
Which plugins are already included in next/core-web-vitals?
eslint-plugin-reacteslint-plugin-react-hookseslint-plugin-nexteslint-plugin-importeslint-plugin-jsx-a11y
You do not need to install these separately.
Gotcha: What happens if I install a plugin that next/core-web-vitals already includes?
- You get duplicate plugin registration, which causes conflicts and unexpected rule behavior.
- Check what the preset includes before manually adding plugins.
- Only add plugins not already bundled by the preset.
How do I scope a plugin to specific file types?
{
files: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
rules: {
"testing-library/await-async-queries": "error",
"testing-library/prefer-screen-queries": "warn",
},
}Use the files property to restrict rules to matching globs.
What does eslint-plugin-tailwindcss provide?
tailwindcss/classnames-ordersorts Tailwind classes in the recommended order.tailwindcss/no-contradicting-classnamecatches conflicts likep-4 p-8.tailwindcss/no-custom-classnameflags classes not in the Tailwind config (often turned off).
How does the Tailwind plugin find my tailwind.config file?
- It looks for
tailwind.config.*in the project root by default. - If your config is in a non-standard location, set the
configoption in the plugin settings. - Without the correct config path, the plugin fails silently.
What is the difference between flat config native plugins and FlatCompat plugins?
- Flat config native plugins export
configs["flat/recommended"]directly. - Older plugins need
FlatCompatwrapping to work with ESLint 9 flat config. - Check each plugin's README for flat config support status.
Gotcha: Why does adding many plugins slow down linting?
- Each plugin adds its own parsing and rule-checking overhead.
- Five or more active plugins can noticeably slow down linting.
- Profile with
TIMING=1 npx eslint .to find the slowest rules and disable those you do not need.
How do I add TypeScript-specific rules via @typescript-eslint?
"@typescript-eslint/no-unused-vars": ["error", { argsIgnorePattern: "^_" }],
"@typescript-eslint/consistent-type-imports": "error",
"@typescript-eslint/no-explicit-any": "warn",These are included via next/typescript but you can override their severity.
Can I use eslint-plugin-testing-library on all files?
- No. Applying it globally flags non-test code with false positives.
- Always scope it with
files: ["**/*.test.*", "**/*.spec.*"].
How do I check which plugin provides a specific rule?
- Rules are prefixed with the plugin namespace:
react/,@typescript-eslint/,jsx-a11y/, etc. - Run
npx eslint --print-config src/app/page.tsxto see all active rules and their sources.
What accessibility rules does jsx-a11y provide?
jsx-a11y/alt-textrequires alt attributes on images.jsx-a11y/anchor-is-validwarns about anchors without validhref.- The plugin checks ARIA roles, keyboard accessibility, and form labels.
Should I use TypeScript ESLint rules or tsconfig strict options?
- Use both. They catch different things.
@typescript-eslintcatches code patterns (unused vars, floating promises, explicitany).tsconfigstrict options catch type-level issues (null checks, implicit any, unchecked index access).
Related
- ESLint Setup for Next.js — base ESLint configuration
- Essential ESLint Rules — configure individual rules
- ESLint + Prettier Integration — avoid formatting conflicts
- Husky & lint-staged — run plugins only on changed files