← All articles
EDITORS Linting and Formatting: Biome, ESLint, Prettier, and... 2026-02-09 · 5 min read · biome · eslint · prettier

Linting and Formatting: Biome, ESLint, Prettier, and Pre-Commit Hooks

Editors 2026-02-09 · 5 min read biome eslint prettier linting formatting husky

Linting and Formatting: Biome, ESLint, Prettier, and Pre-Commit Hooks

Code formatting debates waste time. Automate the formatting, enforce it in CI, and never argue about semicolons again. This guide covers the current state of JavaScript/TypeScript linting and formatting tooling, from choosing tools to enforcing them.

Biome vs ESLint + Prettier

The main choice in 2026 is between Biome (one tool for both linting and formatting) and ESLint + Prettier (two tools working together).

Feature Biome ESLint + Prettier
Speed Very fast (Rust) Slow (JavaScript)
Config files 1 (biome.json) 2-3 (eslint.config.js, .prettierrc, .prettierignore)
Formatting Built-in Prettier
Lint rules ~300 rules 1000+ with plugins
Plugin ecosystem Limited Massive
TypeScript Native Via typescript-eslint
React/JSX Yes Via eslint-plugin-react
CSS/JSON/etc. Yes Prettier handles, ESLint via plugins

Choose Biome When:

Choose ESLint + Prettier When:

Biome Setup

bun add --dev @biomejs/biome
bunx biome init

This creates biome.json:

{
  "$schema": "https://biomejs.dev/schemas/2.0.x/schema.json",
  "organizeImports": {
    "enabled": true
  },
  "formatter": {
    "indentStyle": "space",
    "indentWidth": 2,
    "lineWidth": 100
  },
  "linter": {
    "enabled": true,
    "rules": {
      "recommended": true
    }
  }
}
# Format
bunx biome format --write .

# Lint
bunx biome lint .

# Both at once
bunx biome check --write .

Biome's check command runs formatting, linting, and import sorting in a single pass. It's faster than running ESLint and Prettier separately.

ESLint + Prettier Setup (Flat Config)

ESLint moved to "flat config" (eslint.config.js) in v9. If you're still using .eslintrc, migrate -- the old config format is deprecated.

bun add --dev eslint @eslint/js typescript-eslint prettier eslint-config-prettier
// eslint.config.js
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 to override conflicting rules
  {
    rules: {
      "@typescript-eslint/no-unused-vars": ["error", {
        argsIgnorePattern: "^_",
      }],
    },
  },
];
// .prettierrc
{
  "semi": true,
  "singleQuote": false,
  "trailingComma": "all",
  "printWidth": 100
}

eslint-config-prettier disables all ESLint rules that conflict with Prettier. Always include it last in your config array.

Pre-Commit Hooks

Pre-commit hooks run linting and formatting on staged files before each commit. This catches issues before they reach CI.

Husky + lint-staged

The most popular approach. Husky manages Git hooks, lint-staged runs commands only on staged files.

bun add --dev husky lint-staged
bunx husky init

This creates .husky/pre-commit. Edit it:

#!/bin/sh
bunx lint-staged

Configure lint-staged in package.json:

{
  "lint-staged": {
    "*.{ts,tsx,js,jsx}": [
      "biome check --write --no-errors-on-unmatched"
    ],
    "*.{json,md,yaml}": [
      "biome format --write --no-errors-on-unmatched"
    ]
  }
}

Or with ESLint + Prettier:

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

Lefthook

Lefthook is a faster, configuration-file-based alternative to Husky. It's written in Go and doesn't require npm post-install scripts.

# lefthook.yml
pre-commit:
  parallel: true
  commands:
    lint:
      glob: "*.{ts,tsx,js,jsx}"
      run: bunx biome check --write --no-errors-on-unmatched {staged_files}
      stage_fixed: true
    format:
      glob: "*.{json,md,yaml}"
      run: bunx biome format --write --no-errors-on-unmatched {staged_files}
      stage_fixed: true
bun add --dev lefthook
bunx lefthook install

Lefthook runs commands in parallel by default and has better performance than Husky + lint-staged for large teams. The stage_fixed: true option automatically re-stages files after auto-fixing.

Trade-offs of Pre-Commit Hooks

Pre-commit hooks have a downside: they add time to every commit. For large repos or slow linters, this friction adds up. Some teams skip pre-commit hooks entirely and rely on CI enforcement, using git commit --no-verify only in emergencies.

A good middle ground: run formatting in pre-commit hooks (fast, auto-fixable) and run linting in CI only (slower, may need manual fixes).

Editor Integration

VS Code

Biome has an official VS Code extension that provides format-on-save and inline diagnostics:

// .vscode/settings.json
{
  "editor.defaultFormatter": "biomejs.biome",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "quickfix.biome": "explicit",
    "source.organizeImports.biome": "explicit"
  }
}

For ESLint + Prettier:

{
  "editor.defaultFormatter": "esbenp.prettier-vscode",
  "editor.formatOnSave": true,
  "editor.codeActionsOnSave": {
    "source.fixAll.eslint": "explicit"
  }
}

Commit your .vscode/settings.json to the repository so all team members get the same editor behavior. Use .vscode/extensions.json to recommend the required extensions.

Other Editors

JetBrains IDEs (WebStorm, IntelliJ) have built-in ESLint and Prettier support. Biome support is available via plugin. Neovim users can configure Biome or ESLint through nvim-lspconfig or none-ls.

CI Enforcement

Pre-commit hooks are a convenience, not a guarantee. Developers can skip them with --no-verify. CI is where you actually enforce code quality.

# GitHub Actions
- name: Check formatting
  run: bunx biome format --check .

- name: Lint
  run: bunx biome lint .

Or with ESLint + Prettier:

- name: Lint
  run: bunx eslint .

- name: Check formatting
  run: bunx prettier --check .

Note the difference: in CI, use --check (report errors without fixing). In local development and pre-commit hooks, use --write (auto-fix). CI should never modify code -- it should only verify.

Run formatting and linting checks early in your CI pipeline, ideally in parallel with type checking and tests. They're fast enough that there's no reason to gate them behind slower steps.

Keeping Configs Minimal

The biggest mistake with linting configuration is adding too many rules. Every custom rule is a decision your team has to maintain and debate. Start with the recommended preset and add rules only when you encounter real problems.

// Good: minimal biome.json
{
  "linter": {
    "rules": {
      "recommended": true
    }
  }
}

// Bad: dozens of individually configured rules
// (every rule is a potential debate and maintenance burden)

The same applies to ESLint. Use recommended presets and override sparingly. If you find yourself disabling rules in dozens of files with inline comments, the rule probably doesn't fit your codebase.

Recommendations