Biome vs ESLint vs Oxlint: JS Linters 2026
Biome vs ESLint vs Oxlint: JS Linters 2026
TL;DR
ESLint v9 remains the safe default — 50M+ weekly downloads and an irreplaceable plugin ecosystem. Biome is the best choice for greenfield projects that want one fast tool for linting and formatting (no Prettier needed). Oxlint is the fastest raw linter at 50-100x ESLint's speed, but it's linting-only with ~300 rules and no auto-fix for most violations — use it as a CI speed layer, not a replacement.
Key Takeaways
- ESLint: 50M+ weekly downloads — universal framework support, 700+ rules, 4000+ plugins
- Biome: ~1.5M weekly downloads — 423+ rules, built-in formatter, Rust-based, 10-20x faster than ESLint
- Oxlint: ~500K weekly downloads — 50-100x faster than ESLint, linting only, ~300 rules
- Biome v2 ships type-aware linting — previously only possible with @typescript-eslint
- Biome lints 10,000 files in 0.8 seconds vs ESLint's ~45 seconds
- Oxlint is ~2x faster than Biome for pure linting workloads
- ESLint flat config (eslint.config.js) is now the standard since v9 — old cascading config deprecated
The Rust Tooling Revolution
JavaScript tooling has entered its Rust era. Both Biome and Oxlint are written in Rust and compiled to native binaries — that's the source of their 10-100x speed advantage over Node.js-based ESLint. The performance gap isn't about algorithms; it's about the runtime. Node.js has JIT compilation, garbage collection pauses, and startup overhead that native Rust binaries simply don't have. When parsing and traversing abstract syntax trees for millions of lines of code, these differences compound.
The practical impact: a mid-sized repo that took ESLint 30-45 seconds to lint now takes Biome under 1 second or Oxlint under 0.5 seconds. In CI, this translates directly to developer cost and iteration speed. At $0.008 per CI minute on GitHub Actions, a team running 50 CI jobs per day that each spend 30 seconds on linting pays roughly $438 per year just for ESLint. Biome brings that to under $30. The math isn't hypothetical — multiple engineering teams have published case studies showing 60-80% CI cost reduction from switching linters.
But speed alone doesn't determine the right choice. Linting is fundamentally about catching bugs and enforcing code standards. A fast linter that doesn't catch your bugs is worthless. The question is whether Biome or Oxlint's rule coverage is sufficient for your codebase — and for many teams in 2026, it increasingly is.
For context on the broader tooling landscape, see Best Code Formatting Tools 2026 and OXC vs ESLint vs Biome: JavaScript Linting in 2026.
Comparison Table
| Dimension | ESLint v9 | Biome v2 | Oxlint 0.x |
|---|---|---|---|
| Weekly Downloads | 50M+ | ~1.5M | ~500K |
| Written In | JavaScript | Rust | Rust |
| Linting | Yes (700+ rules) | Yes (423+ rules) | Yes (~300 rules) |
| Formatting | No (needs Prettier) | Yes (built-in) | No |
| Auto-fix | Excellent | Good | Limited |
| Type-aware rules | Yes (@typescript-eslint) | Yes (v2+) | No |
| Plugin ecosystem | 4000+ packages | Growing | Minimal |
| Config complexity | Medium (flat config) | Low | Very low |
| Speed vs ESLint | 1x | 10-20x | 50-100x |
ESLint v9
ESLint turned 13 in 2026 and is more dominant than ever. The v9 release brought flat config — a single eslint.config.js replacing the old cascading .eslintrc.* system — which significantly reduces configuration surprises in monorepos.
// eslint.config.js (flat config, ESLint v9)
import js from "@eslint/js";
import tsPlugin from "@typescript-eslint/eslint-plugin";
import tsParser from "@typescript-eslint/parser";
import reactPlugin from "eslint-plugin-react";
export default [
js.configs.recommended,
{
files: ["**/*.{ts,tsx}"],
languageOptions: {
parser: tsParser,
parserOptions: {
project: "./tsconfig.json",
},
},
plugins: {
"@typescript-eslint": tsPlugin,
},
rules: {
"@typescript-eslint/no-explicit-any": "error",
"@typescript-eslint/strict-boolean-expressions": "warn",
},
},
{
files: ["**/*.{jsx,tsx}"],
plugins: { react: reactPlugin },
rules: {
...reactPlugin.configs.recommended.rules,
"react/react-in-jsx-scope": "off",
},
},
];
ESLint's irreplaceable advantage is its plugin ecosystem. Framework-specific rules (Next.js, Remix, Astro), accessibility (jsx-a11y), security (eslint-plugin-security), and hundreds of specialized plugins have no equivalent in Biome or Oxlint. If you use Storybook, Vitest, or Playwright, there are ESLint plugins that know your APIs. The eslint-plugin-react-hooks rules alone have caught thousands of real bugs in production codebases — Biome ships equivalents, but the React hooks plugin's 12+ years of battle-testing means it handles subtle edge cases that newer implementations may miss.
The v9 flat config migration is worth understanding before committing to ESLint. The old cascading .eslintrc.json system created subtle inheritance bugs in monorepos where config files at different directory levels would combine in unexpected ways. Flat config is explicit: one eslint.config.js at the workspace root, no implicit inheritance, no surprise rule combinations. Teams that have migrated report significantly fewer "why is this rule triggering here?" debugging sessions.
The performance story is improving too. With Vercel's adoption of Oxlint as a pre-pass linter alongside ESLint, teams are finding 60%+ CI improvements without abandoning ESLint's rule depth. The dual-linter pattern (Oxlint fast pass + ESLint for specialized rules) is gaining traction specifically because it preserves ESLint's plugin ecosystem while addressing its performance bottleneck.
ESLint also has the most mature TypeScript integration. @typescript-eslint provides rules that understand your TypeScript types — not just syntax. Rules like no-floating-promises, strict-boolean-expressions, and consistent-return catch real TypeScript bugs that Biome is only beginning to approach with its v2 type-aware rules.
When ESLint is the right choice:
- Any project with specialized plugin requirements (Next.js, jsx-a11y, testing frameworks)
- Teams with existing ESLint configs where migration cost outweighs speed gains
- Monorepos with mixed framework targets needing different rule sets per project
- When @typescript-eslint's type-aware rules are required for safety-critical code
Biome
Biome emerged from the Rome project and has become the most credible ESLint+Prettier replacement for new projects. The v2 release was significant: it added type-aware linting rules (previously exclusive to @typescript-eslint), bringing Biome's capabilities much closer to a full ESLint+typescript-eslint setup.
// biome.json
{
"$schema": "https://biomejs.dev/schemas/2.0.0/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"suspicious": { "noExplicitAny": "error" },
"style": { "useConst": "error" }
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"trailingCommas": "es5"
}
}
}
# Lint + format in one command
npx @biomejs/biome check --write .
# CI (no writes, exit code 1 on violations)
npx @biomejs/biome ci .
Biome's formatter is a drop-in Prettier replacement for most codebases. It formats JavaScript, TypeScript, JSX, JSON, and CSS. The output is intentionally close to Prettier's — migration scripts are available. Where Prettier and Biome differ, it's usually in edge cases involving complex expressions or unusual syntax — for typical TypeScript React codebases, the output is practically identical.
The v2 addition of type-aware linting is Biome's most significant capability expansion. Previously, rules that needed TypeScript type information — like "this function might return undefined but the caller doesn't check" — required @typescript-eslint with a type-checked config, which adds significant parse time because TypeScript's compiler needs to run. Biome v2 achieves similar analysis through its own type inference engine, maintaining its speed advantage while matching more of ESLint's rule coverage.
// Biome understands this TypeScript correctly
function processItems<T extends { id: string }>(items: T[]): Map<string, T> {
return new Map(items.map((item) => [item.id, item]));
}
One practical consideration: Biome's VS Code extension is mature and provides real-time feedback with the same speed advantages as the CLI. Format-on-save with Biome is noticeably faster than with Prettier, which matters for large files or complex TypeScript. Teams that switch often mention the editor experience as an unexpected quality-of-life improvement.
The biggest practical migration challenge from ESLint to Biome is the missing rules. Biome's 423+ rules are growing rapidly, but specialized plugins — eslint-plugin-import, eslint-plugin-testing-library, Storybook-specific rules — either don't exist in Biome or are partial. Audit your current ESLint config before migrating: if you rely on 5+ specialized plugins, Biome may not yet cover enough of your rule set.
When Biome is the right choice:
- New projects where you want zero Prettier + ESLint config overhead
- Teams that prioritize fast CI and local feedback loops
- Projects not requiring more than 5-6 specialized ESLint plugins
- Codebases wanting both linting and formatting in one consistent tool
Oxlint
Oxlint (part of the OXC project) focuses on raw linting speed above all else. At 50-100x faster than ESLint, it can lint a monorepo with 100K lines in under a second. The tradeoff: no formatting, limited auto-fix, ~300 rules, and virtually no plugin ecosystem.
# Install and run
npm install -D oxlint
npx oxlint src/
# With specific rules
npx oxlint --deny=correctness --warn=perf src/
# TypeScript files
npx oxlint --tsconfig=tsconfig.json src/
// .oxlintrc.json
{
"rules": {
"no-unused-vars": "error",
"no-console": "warn",
"react/no-direct-mutation-state": "error"
},
"plugins": ["react", "typescript"],
"ignorePatterns": ["dist/", "node_modules/"]
}
The most pragmatic Oxlint use case in 2026 is the dual-linter approach: Oxlint as a fast pre-pass for obvious errors (runs in 0.3s), then ESLint for type-aware and framework-specific rules (runs in 30s but only on changed files). Vercel's engineering blog documented 60%+ CI time reduction using exactly this pattern. The idea is that most trivial errors — unused variables, wrong equality operators, deprecated APIs — can be caught by Oxlint instantly before ESLint even starts. ESLint then only runs on files that passed Oxlint, and only when the Oxlint pass is clean.
Oxlint's auto-fix capabilities are a known limitation. Where ESLint and Biome can auto-fix the majority of their reported issues, Oxlint's auto-fix coverage is selective. Teams using Oxlint as their primary linter need to be comfortable manually addressing some categories of reported issues. The OXC team is actively expanding auto-fix support, but as of early 2026 it's still behind ESLint and Biome in this area.
Another consideration is Oxlint's configuration system. It's simpler than ESLint's flat config, which is either a feature or a limitation depending on your needs. You can't yet define complex per-directory rule overrides or build a layered config system comparable to ESLint. For simple projects this is fine; for monorepos with different standards across packages, it's a gap.
When Oxlint is the right choice:
- CI pre-pass alongside ESLint for large monorepos where linting is a bottleneck
- Basic linting for simple Node.js scripts or CLIs without framework requirements
- Teams gradually migrating toward Rust-based tooling who want to start simply
- Projects where zero-config basic correctness checks are the primary need
When to Choose Each
Choose ESLint v9 as your default unless you have a compelling reason to switch. Its flat config is cleaner than the old system, and no other tool matches its plugin depth. The performance cost is real but often acceptable — especially with caching enabled via --cache and running only on changed files in CI. ESLint is also the lowest-risk choice: every tutorial, every framework guide, and every team you hire from will know it.
Choose Biome for new projects in 2026 — it's the most mature ESLint+Prettier alternative and v2's type-aware rules close the functionality gap significantly. If your project doesn't need specialized ESLint plugins, Biome is strictly better: faster, simpler, one tool. The developer experience of a single biome check --write for both linting and formatting is genuinely pleasant compared to coordinating ESLint and Prettier separately.
Choose Oxlint as a CI accelerator alongside ESLint, not as a replacement. If your linting is a bottleneck and you can't migrate to Biome, Oxlint's dual-linter pattern offers significant speedups with minimal migration cost. Think of Oxlint as a fast pre-screen, not a full linting solution.
The trajectory matters here: Biome is moving fast. Rules coverage is growing, type-aware linting is new, and the community is maturing. A project that couldn't migrate to Biome six months ago because of a missing plugin might be able to today. It's worth rechecking Biome's rule parity with your current ESLint config every quarter if you're on ESLint and considering a switch.
For migration guidance, see How to Migrate ESLint to Biome and ESLint vs Biome 2026.
Methodology
Performance data sourced from the OXC benchmark repository (oxc-project/bench-javascript-linter) and Biome documentation. Download statistics from npm trends (March 2026). Rule counts from official documentation for Biome v2 and Oxlint 0.x. The 10,000-file benchmark reflects a mid-sized TypeScript monorepo with mixed JSX and utility code.