--- title: "JavaScript/TypeScript Code Style Guide (2026)" description: "Agent-facing linter/formatter decision guidance for 2026: Biome 2.x's type-aware linting (no TS compiler needed), current ESLint/typescript-eslint/eslint-plugin-react-hooks versions, and when ESLint's plugin ecosystem still beats Biome." language: javascript framework: null category: language_best_practices tags: - best-practices - code-style - eslint - biome - naming-conventions - esm - linting keywords: - biome 2.3 type-aware linting biotype - eslint 10 typescript-eslint 8.59 flat config - eslint-plugin-react-hooks v6 flat config - biome vs eslint 2026 decision last_updated: "2026-07-08" difficulty: beginner version: "Biome >= 2.3, ESLint >= 10, typescript-eslint >= 8.59, eslint-plugin-react-hooks >= 6" related: - ./README.md - ./typescript/best_practices.md - ./frameworks/frontend/react_best_practices.md - ./frameworks/backend/nodejs_best_practices.md - ../python/frameworks/fastapi/best_practices.md - ../../core/principles/solid.md - ../../core/devops/autonomous_least_privilege_environments.md search_priority: high status: published --- # JavaScript/TypeScript Code Style Guide (2026) Assumes the reader knows what a linter and a formatter are. This is the current decision, not the definitions. ## The Biome-vs-ESLint gap closed in 2025 - update any prior "Biome lacks type-aware linting" claim Biome 2.0 ("Biotype", late 2025) shipped **type-aware linting without invoking the TypeScript compiler** - it builds its own type inference layer instead of shelling out to `tsc`, which is precisely the performance bottleneck that made `typescript-eslint`'s `recommendedTypeChecked` configs slow on large codebases. Biome 2.3 (January 2026) covers 491 lint rules. Weekly npm downloads for `@biomejs/biome` were ~8.8M as of mid-May 2026. In production at Vercel, Discord, Slack, Coinbase, Astro, and the Node.js project itself. What Biome still doesn't have, as of 2026: a mature third-party plugin ecosystem for framework-specific rules and custom domain rules - `eslint-plugin-react-hooks`, `@next/eslint-plugin-next`, and hand-written organization-specific ESLint rules have no Biome equivalent. That, not "no type awareness," is the real remaining reason to stay on ESLint. | Dimension | Biome 2.3+ | ESLint 10 + typescript-eslint 8.59 | |---|---|---| | Type-aware linting | Yes, without `tsc` in the loop | Yes, via the TS compiler (slower on large repos) | | Framework-specific plugin coverage | Partial | Full (`react-hooks`, `next`, `jsx-a11y`) | | Custom rule authoring | Immature plugin system | Mature, `RuleTester`-based | | Single-pass format+lint | Yes | No (two tools) | **Verdict**: default to Biome for new projects. Stay on ESLint + `typescript-eslint` only where `eslint-plugin-react-hooks` or another framework-specific/custom rule set is load-bearing. ## Current version floor - pin these, not older majors - `eslint@>=10`, `@eslint/js@>=10`, `typescript-eslint@>=8.59` - flat config (`eslint.config.js`) is the only format ESLint 10 ships; `.eslintrc.*` is dead weight to delete, not migrate. - `eslint-plugin-react-hooks@>=6` - defaults to flat config and, notably, **correctly excludes `useEffectEvent` functions from exhaustive-deps warnings** (a real false-positive class in v5 and earlier once `useEffectEvent` shipped in React 19.2). If a codebase pins `eslint-plugin-react-hooks@5` alongside React 19.2+, expect spurious `exhaustive-deps` warnings on every `useEffectEvent` call site. - `@biomejs/biome@>=2.3` for type-aware rules; anything `1.x` predates Biotype and has no type inference at all - treat `1.x` configs as needing a real migration, not a version bump. ```jsonc // biome.json - type-aware rule now available without a tsc dependency { "$schema": "https://biomejs.dev/schemas/2.3.0/schema.json", "formatter": { "enabled": true, "indentStyle": "space", "indentWidth": 2 }, "linter": { "enabled": true, "rules": { "recommended": true, "suspicious": { "noExplicitAny": "error" } } }, "javascript": { "formatter": { "quoteStyle": "double", "semicolons": "asNeeded" } } } ``` ```js // eslint.config.js - flat config, react-hooks v6, type-aware import js from "@eslint/js" import tseslint from "typescript-eslint" import reactHooks from "eslint-plugin-react-hooks" export default tseslint.config( js.configs.recommended, ...tseslint.configs.recommendedTypeChecked, { files: ["**/*.{ts,tsx}"], languageOptions: { parserOptions: { project: "./tsconfig.json" } }, plugins: { "react-hooks": reactHooks }, rules: { "react-hooks/rules-of-hooks": "error", "react-hooks/exhaustive-deps": "warn", "@typescript-eslint/no-floating-promises": "error", }, }, ) ``` ## Naming conventions (unchanged, enforce via lint rule not review) | Construct | Convention | Example | |---|---|---| | Variables, functions | `camelCase` | `getUserById` | | Classes, types, interfaces, enums | `PascalCase` | `UserRepository` | | Constants (module-level, immutable) | `UPPER_SNAKE_CASE` | `MAX_RETRY_ATTEMPTS` | | File names (non-component) | `kebab-case.ts` | `user-repository.ts` | | React component files | `PascalCase.tsx` | `UserProfile.tsx` | | Private class members | `#field` (native private) | `#connectionPool` | Enforce via `@typescript-eslint/naming-convention` or Biome's equivalent - not code review; humans miss it consistently. ## Anti-patterns worth flagging specifically - **Pinning `eslint-plugin-react-hooks@<6` on a React 19.2+ codebase** - produces false-positive `exhaustive-deps` warnings on `useEffectEvent`, training developers to silence real warnings alongside the fake ones. - **Treating install-time devDependency install as automatically safe** - as of npm 12 / pnpm 10+ install scripts are deny-by-default (see [`README.md`](./README.md)'s supply-chain section), but a lockfile pinned to an older package-manager major still executes arbitrary `postinstall` code from any transitive dependency. A linter/formatter choice is itself a supply-chain decision: fewer devDependencies (Biome: one package) is a smaller attack surface than ESLint+Prettier's typical dozen-plus plugin packages. - **Running `.eslintrc.*` and `eslint.config.js` side by side "during migration" indefinitely** - ESLint 10 has fully dropped the legacy cascade; a stale `.eslintrc.*` left in the tree is dead config, not a fallback. ## Sources - [Biome vs ESLint vs Oxlint 2026 - PkgPulse](https://www.pkgpulse.com/guides/biome-vs-eslint-vs-oxlint-2026) - [Biome Replaces ESLint in 2026 - byteiota](https://byteiota.com/biome-replaces-eslint-in-2026-10-20x-faster-linting/) - [What's New in React 19.2 (useEffectEvent, eslint-plugin-react-hooks v6) - dev.to](https://dev.to/manikandan/whats-new-in-react-192-activity-api-useeffectevent-cachesignal-more-1e3e) - [Mitigating supply chain attacks - pnpm docs](https://pnpm.io/supply-chain-security)