ESLint vs Prettier Config Wars — One Setup That Stops Fights

ESLint Prettier tooling

The PR has 40 files changed. Three of them are real logic. The rest are semicolons, quote style, and import order flipping because Alice’s VS Code uses ESLint fix-on-save with an old .eslintrc and Bob’s uses Prettier with a different printWidth. The Slack thread is titled “tabs vs spaces” for the thirteenth time. You do not need a better opinion. You need one pipeline where ESLint does not fight Prettier.

Split responsibilities cleanly

ToolOwnsDoes not own
PrettierSpaces, quotes, wrapping, trailing commasUnused vars, hooks rules, import legality
ESLintCorrectness, bugs, team policyRe-indenting what Prettier already formatted

When both format the same syntax differently, you get ping-pong commits. The fix is eslint-config-prettier: it disables ESLint rules that conflict with Prettier so only one formatter runs.

The boring setup that works in 2026

npm i -D eslint prettier eslint-config-prettier eslint-plugin-prettier
# or: prettier as CLI only, and skip eslint-plugin-prettier if you want faster lint

eslint.config.js (flat config sketch):

import prettier from "eslint-config-prettier";
import js from "@eslint/js";

export default [
  js.configs.recommended,
  // ... typescript / react configs ...
  prettier, // LAST — turns off conflicting stylistic rules
];

.prettierrc:

{
  "singleQuote": true,
  "trailingComma": "all",
  "printWidth": 100
}

Run format and lint as separate scripts:

{
  "scripts": {
    "format": "prettier --write .",
    "lint": "eslint .",
    "check": "prettier --check . && eslint ."
  }
}

eslint-plugin-prettier (run Prettier as an ESLint rule) is optional. It unifies the error channel but slows lint. Many teams prefer Prettier in CI via --check and ESLint for logic only.

Import order: pick one owner

Import sorting is the last battleground. Either:

  • eslint-plugin-simple-import-sort (or import/order) and tell Prettier to leave imports alone, or
  • @trivago/prettier-plugin-sort-imports / prettier-plugin-organize-imports and turn off ESLint import ordering.

Two sorters = eternal war. Document the choice in CONTRIBUTING.md in one sentence.

Editor and CI must match

  1. Commit .vscode/settings.json (or Cursor equivalents) with editor.defaultFormatter for Prettier and source.fixAll on save for ESLint code actions only, not a second formatter.
  2. Fail CI on prettier --check and eslint — not on “looks fine on my machine.”
  3. Same Node/Prettier major version via packageManager / Volta / .nvmrc so CLI and editor plugin do not diverge.
{
  "editor.formatOnSave": true,
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  }
}

Rules that still belong in ESLint

Keep no-unused-vars, react-hooks/exhaustive-deps, @typescript-eslint/no-floating-promises, and security plugins. Delete or disable indent, semi, quotes, comma-dangle — Prettier owns those.

Ending an existing war

  1. Add eslint-config-prettier last in the config chain.
  2. One PR that only runs Prettier across the repo (agree on a merge freeze window).
  3. Delete conflicting local settings from wiki folklore.
  4. Ban “format-only” nit comments in review — the bot already owns that.

Monorepo note

Share one root Prettier config and one ESLint flat config across packages. If a package truly needs a different printWidth (generated GraphQL, huge markdown tables), use Prettier overrides — do not invent a second formatting religion. Run prettier --check and eslint in the same CI job so contributors see both failures together instead of ping-ponging across separate green checks.

Config wars are process failures. Give Prettier the aesthetics, ESLint the bugs, one sort plugin, and identical CI. Then the next PR diff can be about the feature again.