EmpireUI
Get Pro
← Blog7 min read#prettier#typescript#react

Prettier Configuration: Format on Save for React + TypeScript

Set up Prettier with format-on-save for React and TypeScript projects. Real configs, VS Code settings, ESLint integration, and the exact rules that actually work.

Code editor showing formatted TypeScript and React code with syntax highlighting

Why Your Prettier Setup Is Probably Broken

Honestly, most teams have Prettier installed but not actually working — format-on-save silently fails, .prettierrc gets ignored, and half the team formats differently than the other half. That's not a Prettier problem. It's a config problem.

This guide is about getting it right once. We're covering the install, the .prettierrc that won't cause arguments, VS Code settings.json for real format-on-save, and the ESLint integration that stops them fighting each other.

We're using Prettier 3.3.3, ESLint 9.x flat config, and TypeScript 5.5. The concepts apply broadly but the exact flags and plugin names matter — older tutorials get this wrong.

Installing Prettier 3.x in a React + TypeScript Project

Start clean. If you've got an old prettier install floating around, remove it first.

npm remove prettier eslint-plugin-prettier
npm install --save-dev prettier@3.3.3 eslint-config-prettier@9.1.0

Notice we're installing eslint-config-prettier and *not* eslint-plugin-prettier. The plugin runs Prettier as an ESLint rule, which sounds useful but doubles the formatting pass, slows things down, and produces confusing error messages. The config package just turns off ESLint rules that conflict with Prettier — that's all you need.

Also add a .prettierignore immediately. Without it, Prettier will try to format your lock file, generated types, and anything else that shouldn't be touched.

The .prettierrc Config That Won't Cause Arguments

There are three settings teams fight over: semicolons, single vs double quotes, and trailing commas. Here's a config that makes sensible calls and doesn't leave room for debate:

{
  "semi": true,
  "singleQuote": false,
  "jsxSingleQuote": false,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2,
  "useTabs": false,
  "bracketSpacing": true,
  "bracketSameLine": false,
  "arrowParens": "always",
  "endOfLine": "lf"
}

printWidth: 100 instead of the default 80 makes sense for TypeScript — generic types eat horizontal space fast. trailingComma: "all" is the modern default since Prettier 3.0 anyway, but being explicit avoids surprises when someone opens the project with an older local install.

endOfLine: "lf" is non-negotiable if you have Windows and Mac devs on the same repo. Without it you'll see diffs full of CRLF noise that have nothing to do with actual code changes.

VS Code Format on Save: The Exact Settings You Need

Format-on-save stops working for one of three reasons: the Prettier extension isn't set as the default formatter, the workspace settings override the user settings, or the language-specific formatter is set to something else. Here's the .vscode/settings.json that covers all three:

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.formatOnPaste": false,
  "[typescript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[typescriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascript]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[javascriptreact]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[json]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "[css]": {
    "editor.defaultFormatter": "esbenp.prettier-vscode"
  },
  "prettier.requireConfig": true
}

The prettier.requireConfig: true setting is important. It means Prettier won't format files in projects that don't have a .prettierrc — so if you open someone else's unformatted project, it won't silently rewrite everything.

Commit this file to the repo. Every teammate gets the same behavior without having to configure anything locally. That's the whole point.

ESLint + Prettier: Getting Them to Stop Fighting

ESLint and Prettier have overlapping opinions about code style — things like spacing, quotes, and semicolons. If you don't align them, you'll save a file, Prettier formats it one way, then ESLint immediately marks it as an error. It's maddening.

With ESLint 9 flat config, the setup looks like this:

// eslint.config.mjs
import js from "@eslint/js";
import tseslint from "typescript-eslint";
import prettierConfig from "eslint-config-prettier";

export default [
  js.configs.recommended,
  ...tseslint.configs.recommended,
  prettierConfig, // must be last — it turns off conflicting rules
  {
    rules: {
      "@typescript-eslint/no-unused-vars": ["warn", { argsIgnorePattern: "^_" }],
      "@typescript-eslint/no-explicit-any": "warn",
    },
  },
];

prettierConfig has to be last in the array. It disables specific ESLint rules that Prettier already handles — if another config runs after it and re-enables those rules, you're back to the fighting state.

If you're working on a component library like Empire UI where visual consistency matters, clean formatting also makes code review faster. Reviewers can focus on logic rather than style nitpicks.

Formatting Tailwind Classes with prettier-plugin-tailwindcss

If your project uses Tailwind — and if you're using React in 2026 there's a good chance it does — add prettier-plugin-tailwindcss. It sorts class names in the official recommended order automatically.

npm install --save-dev prettier-plugin-tailwindcss@0.6.11

Then update your .prettierrc:

{
  "semi": true,
  "singleQuote": false,
  "trailingComma": "all",
  "printWidth": 100,
  "tabWidth": 2,
  "plugins": ["prettier-plugin-tailwindcss"],
  "tailwindConfig": "./tailwind.config.ts"
}

This becomes genuinely useful when you're working with design tokens or utility-heavy components. Tools like the gradient generator or box shadow generator produce utility class strings — having them sorted consistently means merges are cleaner and you can spot duplicate classes at a glance.

Enforcing Formatting in CI with a Pre-commit Hook

Format-on-save is great for your local machine. It does nothing for teammates who don't use VS Code, or for commits pushed from the command line. That's where a pre-commit hook matters.

npm install --save-dev husky@9.1.7 lint-staged@15.2.10
npx husky init

Then in package.json:

{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": ["prettier --write", "eslint --fix"],
    "*.{json,css,md}": ["prettier --write"]
  }
}

And in .husky/pre-commit:

npx lint-staged

Now every commit is formatted before it lands. The CI check becomes a backstop, not the first line of defense. When you're building something like a theme toggle in React where JSX gets complex fast, having auto-format on commit keeps the diff readable.

Running Prettier from the Command Line and in CI

Add these scripts to package.json so anyone on the team can run them without knowing the flags:

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

prettier --check exits with a non-zero code if any file is unformatted. Drop npm run format:check into your CI pipeline — GitHub Actions, Dokploy, whatever you're using — and unformatted code simply can't merge.

Is it strict? Yes. Does it prevent the 200-line diff that's actually just whitespace changes? Also yes. That trade-off is worth it every time.

One last thing: don't add prettier --write to CI. It should only check, not fix. Fixes belong in the developer's local environment or in the pre-commit hook. A CI step that silently rewrites files creates confusing state where the checked-out code doesn't match what passed.

FAQ

Why isn't Prettier formatting on save in VS Code even though the extension is installed?

Usually one of three things: the Prettier extension isn't set as the default formatter for that file type, editor.formatOnSave is false in your workspace settings, or another formatter (like the TypeScript language server) is winning. Add explicit [typescript] and [typescriptreact] entries in .vscode/settings.json that point to esbenp.prettier-vscode and set editor.defaultFormatter at the top level too.

Should I use eslint-plugin-prettier or eslint-config-prettier?

Use eslint-config-prettier only. The plugin version runs Prettier as an ESLint rule, which slows linting and produces misleading error messages. The config package just disables ESLint rules that conflict with Prettier — cleaner, faster, and easier to debug.

Prettier 3 changed `trailingComma` defaults — do I need to specify it?

Prettier 3.0 changed the default from es5 to all, so trailing commas now appear on function parameters too. If your team is all on Prettier 3.x you technically don't need to specify it, but being explicit in .prettierrc means the config is self-documenting and won't surprise someone who looks at it cold.

Does prettier-plugin-tailwindcss work with Tailwind v4?

Yes, but you need at least prettier-plugin-tailwindcss@0.6.x which added v4 support. Point the tailwindConfig option at your config file. If you're using Tailwind v4's CSS-first config (the @theme block instead of tailwind.config.ts), set tailwindStylesheet to your main CSS entry point instead.

How do I stop Prettier from formatting generated files like GraphQL schemas or Prisma client output?

Add them to .prettierignore. Common entries: node_modules, .next, dist, build, *.generated.ts, src/gql/, prisma/migrations. Same syntax as .gitignore.

My pre-commit hook runs but doesn't seem to format files before the commit. What's wrong?

Check that lint-staged is in your package.json and that the .husky/pre-commit file contains npx lint-staged (not just prettier). Also confirm Husky is initialized — run npx husky init if you're not sure. If you're on a monorepo, lint-staged needs to run from the package root, not the repo root.

Free components in 40 styles
React & Tailwind, copy-paste ready.
Browse →

Read next

VS Code React Snippets: Custom Templates That Save HoursBest VS Code Extensions for React Developers in 2026TypeScript Strict Mode: Every Flag and What It CatchesReact UI Components Complete Reference: 60+ Patterns with Code