Biome vs ESLint + Prettier: The Linter Wars 2026
·PkgPulse Team
TL;DR
Biome hasn't replaced ESLint + Prettier yet, but the gap is closing fast. Biome lints and formats 25x faster than ESLint + Prettier, ships with 300+ rules, and requires zero configuration. The blockers: no plugin ecosystem, missing a handful of ESLint rules popular codebases rely on, and no Prettier plugin support (SVGO, Tailwind class sorting). For greenfield projects: Biome is the better default. For existing projects with custom ESLint configs: migration cost is real.
Key Takeaways
- Biome: 1.2M downloads/week growing fast, 25x faster, zero config, 300+ rules
- ESLint: 30M downloads/week, 10,000+ plugins, de-facto standard
- Prettier: 20M downloads/week, near-universal formatting standard
- Biome rule coverage: ~80% of ESLint + 90% of Prettier's formatting
- Migration blocker: No
eslint-plugin-tailwindcss,eslint-plugin-import, custom rules - Verdict: Greenfield → Biome; existing complex config → stay on ESLint until Biome v2
Downloads
| Package | Weekly Downloads | Trend |
|---|---|---|
eslint | ~30M | → Stable |
prettier | ~20M | → Stable |
@biomejs/biome | ~1.2M | ↑ Fast growing |
oxlint | ~400K | ↑ Fast growing |
Biome: The All-In-One Tool
npm install --save-dev --save-exact @biomejs/biome
npx @biomejs/biome init
// biome.json — zero-config to start:
{
"$schema": "https://biomejs.dev/schemas/1.9.0/schema.json",
"organizeImports": { "enabled": true },
"linter": {
"enabled": true,
"rules": {
"recommended": true,
"correctness": { "noUnusedVariables": "warn" },
"style": { "useConst": "error" },
"suspicious": { "noExplicitAny": "warn" }
}
},
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 100
},
"javascript": {
"formatter": {
"quoteStyle": "single",
"trailingCommas": "es5",
"semicolons": "always"
}
}
}
# Run both lint + format check together:
npx @biomejs/biome check --apply .
# Format only:
npx @biomejs/biome format --write .
# Lint only:
npx @biomejs/biome lint --apply .
Speed Benchmark
Real-world project: 500 TypeScript files
ESLint + Prettier (combined):
lint: 18.2s
format: 4.3s
total: ~22s
Biome (lint + format):
total: 0.87s
Speed improvement: ~25x
ESLint + Prettier: The Established Standard
npm install --save-dev eslint prettier eslint-config-prettier \
@typescript-eslint/parser @typescript-eslint/eslint-plugin \
eslint-plugin-react eslint-plugin-react-hooks \
eslint-plugin-import eslint-plugin-tailwindcss
// eslint.config.js (flat config — required in 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';
import reactHooksPlugin from 'eslint-plugin-react-hooks';
import importPlugin from 'eslint-plugin-import';
import tailwindPlugin from 'eslint-plugin-tailwindcss';
import prettierConfig from 'eslint-config-prettier';
export default [
js.configs.recommended,
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: tsParser,
parserOptions: {
project: './tsconfig.json',
},
},
plugins: {
'@typescript-eslint': tsPlugin,
react: reactPlugin,
'react-hooks': reactHooksPlugin,
import: importPlugin,
tailwindcss: tailwindPlugin,
},
rules: {
...tsPlugin.configs.recommended.rules,
...reactHooksPlugin.configs.recommended.rules,
'tailwindcss/classnames-order': 'warn',
'import/order': ['error', { 'newlines-between': 'always' }],
'@typescript-eslint/no-explicit-any': 'warn',
},
},
prettierConfig, // Must be last — disables style rules that conflict with Prettier
];
// .prettierrc:
{
"semi": true,
"trailingComma": "es5",
"singleQuote": true,
"tabWidth": 2,
"printWidth": 100,
"plugins": ["prettier-plugin-tailwindcss"] // Auto-sorts Tailwind classes!
}
Rule Coverage Comparison
| Category | Biome | ESLint ecosystem |
|---|---|---|
| TypeScript rules | ~200 rules | 200+ (typescript-eslint) |
| React hooks | ✅ | ✅ (plugin-react-hooks) |
| Import ordering | ✅ | ✅ (plugin-import) |
| Tailwind class sorting | ❌ | ✅ (prettier-plugin-tailwindcss) |
| Accessibility (jsx-a11y) | ❌ | ✅ |
| Custom rules | ❌ | ✅ (unlimited plugins) |
| Unicorn rules | Partial | ✅ (eslint-plugin-unicorn) |
| Security rules | ❌ | ✅ (eslint-plugin-security) |
Migration Path: ESLint → Biome
# Check Biome compatibility before migrating:
npx @biomejs/biome migrate eslint --write
# This reads your .eslintrc and generates biome.json equivalent
# Output shows which rules map and which don't
Migration output example:
✅ 47 rules migrated
⚠️ 8 rules have no Biome equivalent:
- tailwindcss/classnames-order → Use Prettier plugin instead
- import/order → Biome has organize-imports (different behavior)
- jsx-a11y/* → Not yet supported in Biome
- custom-plugin/my-rule → Custom rules not supported
Hybrid Approach (Practical)
// Use Biome for fast lint+format, ESLint only for gaps:
{
"scripts": {
"lint": "biome check . && eslint --max-warnings 0 src/",
"format": "biome format --write ."
}
}
// Minimal eslint.config.js — only what Biome can't do:
import tailwindPlugin from 'eslint-plugin-tailwindcss';
import a11y from 'eslint-plugin-jsx-a11y';
export default [
{ plugins: { tailwindcss: tailwindPlugin, 'jsx-a11y': a11y } },
{ rules: {
'tailwindcss/classnames-order': 'warn',
...a11y.configs.recommended.rules,
}},
];
Decision Guide
Choose Biome if:
→ New project with no legacy ESLint config
→ Speed is important (CI time, pre-commit hooks)
→ Don't need Tailwind class sorting or a11y rules
→ Team prefers zero-config defaults
Stay with ESLint + Prettier if:
→ Need eslint-plugin-tailwindcss (class sorting)
→ Accessibility rules (jsx-a11y) are required
→ Custom ESLint rules or plugins
→ Large existing codebase with lots of eslint-disable comments
Hybrid (best of both):
→ Biome for formatting + TypeScript/React rules
→ ESLint for Tailwind sorting + a11y only
→ Significant speed gains with minimal migration friction
Compare Biome, ESLint, and Prettier download trends on PkgPulse.