React Code Formatting
Overview
Unifies CLI lint and save-time formatting for React (non-Next) projects using:
- ESLint flat config (
eslint-plugin-react+ TypeScript;eslint-config-prettierdisables conflicting rules) - Prettier (formatting via Prettier CLI / editor, not
eslint-plugin-prettier) - Husky + lint-staged + commitlint
- VSCode/Cursor extension and settings checks
For Next.js projects, use the next-formatting skill instead.
When to Use
Use when package.json has react but no next, and:
npm run lintconflicts with editor save formatting- the project needs standardized formatting and commit hooks
- commit messages are rejected or inconsistent
- VSCode/Cursor plugins may be missing or misconfigured
Workflow
- Install and configure ESLint + Prettier (React-specific)
- Configure Husky + lint-staged + commitlint
- Verify VSCode/Cursor plugins and settings
- Run verification commands and report gaps
1) ESLint + Prettier
Install
npm install -D prettier eslint-config-prettier \
@eslint/js typescript-eslint eslint-plugin-react eslint-plugin-react-hooks \
eslint-plugin-jsx-a11y globals
If Tailwind is used:
npm install -D prettier-plugin-tailwindcss
Prettier
Create prettier.config.mjs:
const config = {
singleQuote: false,
tabWidth: 2,
trailingComma: "all",
printWidth: 100,
semi: true,
arrowParens: "avoid",
bracketSameLine: true,
plugins: ["prettier-plugin-tailwindcss"],
};
export default config;
Create .prettierignore for generated artifacts (node_modules, dist, build, etc.).
ESLint (flat config)
Create eslint.config.mjs:
import js from "@eslint/js";
import globals from "globals";
import react from "eslint-plugin-react";
import reactHooks from "eslint-plugin-react-hooks";
import jsxA11y from "eslint-plugin-jsx-a11y";
import tseslint from "typescript-eslint";
import eslintConfigPrettier from "eslint-config-prettier/flat";
export default [
js.configs.recommended,
...tseslint.configs.recommended,
{
files: ["**/*.{js,jsx,ts,tsx}"],
languageOptions: {
globals: { ...globals.browser, ...globals.node },
parserOptions: { ecmaFeatures: { jsx: true } },
},
plugins: { react, "react-hooks": reactHooks, "jsx-a11y": jsxA11y },
settings: { react: { version: "detect" } },
rules: {
...react.configs.recommended.rules,
...reactHooks.configs.recommended.rules,
...jsxA11y.configs.recommended.rules,
},
},
eslintConfigPrettier,
];
Package roles:
@eslint/js— base JavaScript recommended rulestypescript-eslint— TypeScript parser and ruleseslint-plugin-react/react-hooks/jsx-a11y— React, Hooks, and accessibility (similar coverage to whateslint-config-nextbundles for Next)globals— browser/node global names forno-undefeslint-config-prettier— turns off ESLint rules that conflict with Prettier; formatting is handled by Prettier separately (not viaeslint-plugin-prettier)
2) Scripts + Husky + lint-staged + commitlint
Scripts
{
"scripts": {
"lint": "eslint .",
"lint-fix": "eslint . --fix",
"format-fix": "prettier --write .",
"commitlint": "commitlint --edit",
"prepare": "husky"
}
}
Husky + lint-staged
npm install -D husky lint-staged
npx husky init
.husky/pre-commit:
#!/usr/bin/env sh
npx lint-staged
lint-staged.config.mjs:
const lintStagedConfig = {
"*.{js,jsx,ts,tsx}": ["npm run lint-fix"],
"*.{json,css,md}": ["npm run format-fix"],
};
export default lintStagedConfig;
Commitlint
npm install -D @commitlint/cli @commitlint/config-conventional
commitlint.config.mjs:
/** @type {import("@commitlint/types").UserConfig} */
const config = {
extends: ["@commitlint/config-conventional"],
};
export default config;
.husky/commit-msg:
#!/usr/bin/env sh
npx --no -- commitlint --edit "$1"
Hooks: pre-commit → lint-staged; commit-msg → commitlint.
Conventional Commits format: <type>(<scope>): <description> — types include feat, fix, docs, chore, etc.
3) VSCode/Cursor Checks (Required)
Required extensions
esbenp.prettier-vscodedbaeumer.vscode-eslintrvest.vs-code-prettier-eslint
Check with cursor --list-extensions or code --list-extensions. If missing, tell the user which IDs to install.
Workspace settings
Verify .vscode/settings.json has:
editor.formatOnSave: true- language-specific formatters (Prettier ESLint for JS/TS/React when using that workflow)
eslint.validateincludes JS/TS/React variants"*.css": "tailwindcss"infiles.associationswhen using Tailwind
Propose exact JSON edits if missing or conflicting.
4) Verification Before Completion
npm run lint
npm run lint-fix
npm run format-fix
Test hooks (run npm run prepare first if hooks do not fire):
echo "bad commit message" | npx commitlint # fail
echo "chore: verify commitlint" | npx commitlint # pass
git commit --allow-empty -m "invalid message" # commit-msg hook fail
git commit --allow-empty -m "chore: verify hook" # pass
Stage a file and commit to confirm pre-commit runs lint-staged.
Report what was configured, what was verified, and any unresolved gaps.